Repository navigation
Redact credentials from toString() of authorization, encryption and webhook structures - #224
Conversation
Remove the vacuous upload-request toString test.
sophiecarreras
left a comment
There was a problem hiding this comment.
Thanks Felipe. Review from Sofia, with Claude's help. No blocking bug found; this is a comment, not a request for changes on the code, but the CHANGELOG needs a fix.
What was verified (by running, on head ccdb191, JDK 17, offline): 7 test classes pass: B2EventNotificationRuleTest, B2HeadersImplTest, B2AccountAuthorizationTest, B2WebhookConfigurationTest, B2FileSseForRequestTest, B2CustomWebhookHeaderTest, B2AccountAuthorizationCacheTest. Not verified: that the tests fail without the fix. My revert attempt did not compile, so it proved nothing; this rests on reading the tests (they assert the secret is absent and [redacted] present). The deliberate-break results in the PR body are Felipe's, not re-run.
Please change
- CHANGELOG is incomplete.
B2EventNotificationRule.java:89now orders and dedupes bymaxEventsPerBatch. The oldtoStringnever included it, so rules differing only in batch size used to collapse in aTreeSetand no longer do. Also, existing rules are reordered because the order is no longer string-based (url, then headers, then secret). Callers who persist or diff ordered rules will see it. Please state both.
Nits
B2EventNotificationRule.java:109-120: theinstanceofcheck admits subclasses ofB2WebhookConfiguration, whileequalsusesgetClass(). A subclass compared to a webhook uses field comparison, to another type uses class name: transitivity breaks andcompareTocan be 0 whileequalsis false. UsegetClass()equality.- Unknown target types fall back to
Object.toString(class@hash), so they order by identity. Pre-existing, not a regression. B2WebhookCustomHeader.java:20-22:compareToignores name case,equalsdoes not; both throw NPE on a null value though the constructor accepts null. The new header-set comparison inherits this.B2HeadersImpl.java:41-44: the redaction list isAuthorizationand the SSE-C key header only. I found no other secret header inB2Headers.
Checked by reading, no issue: B2UploadUrlResponse, B2UploadPartUrlResponse, B2DownloadAuthorization, B2CreateKeyRequest, B2ApplicationKey, B2CreatedApplicationKey have no toString; B2FileSseForResponse prints only the MD5; JSON output is unchanged; toString was only used as behavior in the old comparator.
Needs a human: other modules (httpClientCore, samples, ITs) that print or assert on these toString outputs; whether anyone relies on the old ordering or the collapse; the full :b2-sdk-core:test in CI (only 7 classes were run); other logging paths in the HTTP client that dump headers (not audited outside core).
…nd document the ordering change Compare webhook settings only when both target configurations have the exact webhook class. Document batch-size deduplication and the changed rule ordering.
sophiecarreras
left a comment
There was a problem hiding this comment.
Re-review of the new head adede03 (Sofia, with Claude's help). No blocking findings from this review.
Both items I asked for are fixed:
- CHANGELOG now says rules differing only in
maxEventsPerBatchno longer collapse, and that the order of existing rules can change. - The comparator uses
getClass() == B2WebhookConfiguration.class, and new tests cover theTreeSetsize andcompareTo/equalsconsistency for a webhook subclass.
Run: B2EventNotificationRuleTest, B2HeadersImplTest, B2AccountAuthorizationTest, B2WebhookConfigurationTest, B2CustomWebhookHeaderTest and B2FileSseForRequestTest at adede03; they pass. Not run: the full :b2-sdk-core:test and the other modules (CI is still pending), and I did not re-verify that the tests fail without the fix.
Still open, not blockers: B2WebhookCustomHeader compareTo ignores header-name case while equals does not, and both throw NPE on a null value the constructor accepts. A human should still check other modules that print or assert on these toString outputs.
I'm leaving this as a comment, not an approval, so the approval stays with the owners.
sophiecarreras
left a comment
There was a problem hiding this comment.
Thanks for this, it looks good to me. I went through every toString() in the SDK and these five structures are the ones that print a credential: the account authorization token, the SSE-C key, the webhook signing secret and custom header values, and the Authorization and SSE-C key headers. I did not find other leaks. The redaction handles null and header-name case correctly and leaves equals, hashCode and serialization unchanged. The rule comparator is a total order consistent with equals, and it no longer collapses rules that differ only by batch size or only by a secret in sorted sets. I reverted the production changes locally and the new tests fail as expected; the full test suite and check_code pass with them applied.
One note: callers that persist or diff ordered rules may see a different order, which the CHANGELOG already mentions. Optional nit: the [redacted] constant is repeated in five classes, so a shared helper would be tidier.
sophiecarreras
left a comment
There was a problem hiding this comment.
Thanks for this, it looks good to me. I went through every toString() in the SDK and these five structures are the ones that print a credential: the account authorization token, the SSE-C key, the webhook signing secret and custom header values, and the Authorization and SSE-C key headers. I did not find other leaks. The redaction handles null and header-name case correctly and leaves equals, hashCode and serialization unchanged. The rule comparator is a total order consistent with equals, and it no longer collapses rules that differ only by batch size or only by a secret in sorted sets. I reverted the production changes locally and the new tests fail as expected; the full test suite and check_code pass with them applied.
One note: callers that persist or diff ordered rules may see a different order, which the CHANGELOG already mentions. Optional nit: the [redacted] constant is repeated in five classes, so a shared helper would be tidier.
Context
Several structures that hold credentials print them in
toString():B2AccountAuthorization(authorization token),B2FileSseForRequest(SSE-C customer key),B2WebhookConfiguration(HMAC signing secret),B2WebhookCustomHeader(header values) andB2HeadersImpl(AuthorizationandX-Bz-Server-Side-Encryption-Customer-Key). Applications that log these objects, or exceptions and debug output that include them, write those values in clear text.Changes
toString()replaces the sensitive values with[redacted](anullvalue still prints asnull). Non-secret fields, including the SSE-C key MD5, are unchanged.B2HeadersImplmatches header names case-insensitively.B2EventNotificationRule.COMPARATORpreviously ordered rules by the target configuration'stoString(). With the redaction, two rules that differ only in a secret would compare equal and one would be dropped from aTreeSet. Webhook targets are now compared field by field (url, custom headers, signing secret,maxEventsPerBatch). Any other target type falls back totoString()ordering, as before, and never throws.CHANGELOG.md: an[Unreleased]entry for both behavior changes.No public API or serialized form changes; JSON output is unaffected.
Verification
toString()omits its secret and keeps non-secret fieldsB2AccountAuthorizationTest,B2FileSseForRequestTest,B2WebhookConfigurationTest,B2CustomWebhookHeaderTest,B2HeadersImplTestB2HeadersImplTestTreeSet;compareTois consistent withequalsB2EventNotificationRuleTestB2EventNotificationRuleTest.testCompareToOrdersUnknownTargetConfigurationsWithoutThrowingEach fix was checked against a deliberate break: restoring the comparator's
toString()ordering fails three tests; removing the header redaction failsB2HeadersImplTest; restoring an exception for unknown targets fails 1 of 702 core tests.