Skip to content

fix(logging): record metadata values that were silently dropped - #1436

Open
bmc08gt wants to merge 2 commits into
code/cashfrom
fix/logging-nullable-metadata
Open

fix(logging): record metadata values that were silently dropped#1436
bmc08gt wants to merge 2 commits into
code/cashfrom
fix/logging-nullable-metadata

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

MetadataBuilder declared infix fun String.to(value: Any). A non-null parameter makes the member inapplicable to a nullable argument, so the call resolves to kotlin.to instead, builds a Pair and discards it in statement position. The field never reaches the log. It compiles clean and produces at most an unused-expression warning, so nothing flagged it.

The declared type is what breaks it, not the runtime value. A String? holding "hello" was dropped just as silently as one holding null.

The fix

Adding an Any? overload alongside the existing Any is not possible — both erase to to(String, Object)V, and Kotlin rejects the platform declaration clash. The single member is widened to Any? instead, recording NULL_PLACEHOLDER for a null. That keeps it the only applicable candidate, so kotlin.to can no longer win for any String key.

Audit

kotlin.to was temporarily shadowed inside the builder with a @Deprecated candidate, which makes exactly the fallback call sites diagnose while leaving correct ones silent. Compiling every module's main and unit-test sources found eight sites dropping fields:

Site Fields
NotificationService title, body
CoinbaseOnRampController correlationId, responseBody, errorLink
GooglePlayBillingClient purchases
SwapViewModel errorMessage
RealVerifiedFiatCalculator balance quarks

Each is now explicit at the call site rather than leaning on the placeholder, so an absent value reads as what the field means: -1 for a count, "none" for an amount, "" where the file already used orEmpty.

What is not recorded

These traces reach breadcrumb sinks for every TraceType except Silent, so unmasking a dropped field can start sending content to Bugsnag that was previously discarded by accident.

Push content stays out: title and body become the derived silent and has_body. The Coinbase responseBody is kept, matching the sibling path in the same file — the error envelope is a fixed schema of correlationId, errorType, errorLink, code and message, and it is most useful exactly when parse() returns null.

The second commit is a separate leak the audit surfaced rather than a nullability bug. GiftCardFundingWorker recorded giftCard.entropymnemonic.getBase58EncodedEntropy(), the seed controlling the gift card's funds — in both funding traces. It is non-null, so it was reaching Bugsnag on every success and failure. It now logs the vault public key, which identifies the same account and is already public on-chain.

Tests

MetadataBuilderTest covers a String? holding a value and one holding null, plus nullable non-String types. Five of its seven tests fail if the parameter is narrowed back to Any.

MetadataBuilder declared `infix fun String.to(value: Any)`. A non-null parameter
makes the member inapplicable to a nullable argument, so the call resolved to
kotlin.to instead, built a Pair and discarded it in statement position. The field
never reached the log. It compiles clean and produces at most an unused-expression
warning, so nothing flagged it.

The declared type is what breaks it, not the runtime value: a String? holding
"hello" was dropped just as silently as one holding null.

An overload taking Any? alongside the existing Any is not possible — both erase to
to(String, Object)V and Kotlin rejects the platform declaration clash. Widen the
single member to Any? instead, recording NULL_PLACEHOLDER for a null. That keeps
it the only applicable candidate, so kotlin.to can no longer win.

Audited the repo by temporarily shadowing kotlin.to inside the builder with a
@deprecated candidate and compiling every module. Eight sites were dropping fields:

  NotificationService          title, body
  CoinbaseOnRampController     correlationId, responseBody, errorLink
  GooglePlayBillingClient      purchases
  SwapViewModel                errorMessage
  RealVerifiedFiatCalculator   balance quarks

Each is now explicit at the call site rather than leaning on the placeholder, so
an absent value reads as what the field means: -1 for a count, "none" for an
amount, "" where the file already used orEmpty. Push content stays out —
TraceType.Process reaches breadcrumb sinks, so title and body become the derived
`silent` and `has_body`.

MetadataBuilderTest covers both nullable cases. Five of its seven tests fail if
the parameter is narrowed back to Any.
GiftCardFundingWorker recorded `giftCard.entropy` in both funding traces. That
value is mnemonic.getBase58EncodedEntropy(), the seed controlling the gift card's
funds, and trace() with any type other than Silent forwards its metadata to
breadcrumb sinks. It was reaching Bugsnag on every funding success and failure.

The vault public key identifies the same account for debugging and is already
public on-chain.

The WorkManager tag built in tagFor still embeds entropy. That stays in the
WorkManager database rather than going to a breadcrumb sink, so it is left alone
here.
@bmc08gt bmc08gt self-assigned this Sep 9, 2026
@github-actions github-actions Bot added type: fix Bug fix area: payments Payments, transfers, intents, billing area: network gRPC, connectivity, API, exchange rates area: onramp Deposit, purchase, Coinbase, fiat on-ramp area: notifications Push notifications, in-app messaging area: tokens Token accounts, balances, token info and removed type: fix Bug fix labels Sep 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: network gRPC, connectivity, API, exchange rates area: notifications Push notifications, in-app messaging area: onramp Deposit, purchase, Coinbase, fiat on-ramp area: payments Payments, transfers, intents, billing area: tokens Token accounts, balances, token info

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant