logger: close three silent no-ops in RedactJSON and MaskJSON - #244
Merged
Conversation
Non-string values on a nested key path were skipped, key matching was case-sensitive while encoding/json field binding is not, and a repeated key kept its earlier copies. Each one failed silently: the key path was listed, nothing was redacted, and nothing said so. RedactJSON and MaskJSON now share one key-path walker. Redaction no longer consults the value type; masking reads a number or boolean through its literal text. Every member matching a key is visited, and members repeating a key collapse into one. New CaseInsensitiveKeys() option, off by default, on a variadic ...Option both signatures gained, so every existing call site compiles and behaves as before. Also escapes replacement values with json.Marshal. Building them as MustParse(`"` + value + `"`) panicked on any value carrying a quote, which a partially masked name can still hold. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
yanyi-wego
approved these changes
Aug 27, 2026
yanyi-wego
left a comment
Contributor
There was a problem hiding this comment.
👍
🤖 AI-assisted review via Claude Code
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
RedactJSONandMaskJSONmiss three classes of value a caller has explicitly asked them to remove. Every one of them fails silently: the key path is listed, nothing is redacted, and nothing tells you.Non-string values on a nested key path are skipped.
getJSONValuereadsGetStringBytes, which is""for a number, a boolean, null, an object and an array, and theif value != ""guard then skips theSet. So{"customer", "latitude"}does nothing to{"customer":{"latitude":12.971598}}. OnlyRedactJSON's single-key branch calledSetunconditionally, so a top-level number was redacted and the same number one level down was not.Key matching is byte for byte, request binding is not.
encoding/jsonbinds struct fields case-insensitively, so{"Customer":{"LATITUDE":1.5}}reaches the handler and is persisted exactly like the lower-case body, but a lower-case entry in the key list does not match it in the raw bytes being logged.A repeated key keeps its earlier copies.
{"customer":{"latitude":1.5},"customer":{"latitude":2.5}}parses with both members present.Getreturns the first,Setreplaces only the first, andMarshalTore-emits both.encoding/jsonbinds the last, so the copy the handler actually accepted is the one that survives into the log.Why
A service of ours now takes two device-precision coordinates on a request body. They are PII and they are JSON numbers, so point 1 means they cannot be removed through the key list at all, and that service is carrying a local redaction pass to work around it. Raised in review by Yan Yi: the fastjson write path already handles any value type, so the gap is here and the local pass should go.
The next change on the same endpoint adds a postal address and a tax identification number, both strings, one of them with mixed-case spelling in the API contract. Point 2 covers those.
How
RedactJSONandMaskJSONnow share one key-path walker,replaceLeaves.encoding/jsonwould have bound, the last of them. A key present once keeps its position in the object.CaseInsensitiveKeys()option, off by default. Both functions gained a variadic...Option, so every existing call site compiles and behaves exactly as before. Pass it when the input is a body bound withencoding/json.[]at the last position now fans out forRedactJSONtoo. It was a no-op there whileMaskJSONhandled it.One fix outside those three, on the same lines: the replacement was built as
fastjson.MustParse("+ value +"), which panics on any value carrying a quote or a backslash. Masking keepsFirstCharsToShowandLastCharsToShowverbatim, so the value"Joker"masked at 2/2 produced""J*r""and panicked inside the logging middleware. Replacements go throughjson.Marshalnow.Tests
New
json_keys_test.gocovering each of the three gaps plus the escaping. All existing tests and golden files pass unchanged, with no edit to any of them.Benchmarks, median of 6 runs at
-benchtime=300ms:Compatibility
Source-compatible. The only behaviour change for a caller that does not pass the new option is that key paths which used to silently do nothing now do what the caller asked, which can only redact more, never less.
@yanyi-wego, please review. I will tag
logger/v0.3.10after merge and drop the local pass in payments on top of it.