logger: record field sensitivity instead of destroying it - #1731
Open
paulwe wants to merge 1 commit into
Open
Conversation
|
(logger.sensitivity) annotations are enforced by replacing the value with "<redacted>". That is right for console and JSON logs, but it also destroys data that a columnar sink could hold under a tag and filter per consumer. Adds SensitiveObjectEncoder / SensitiveArrayEncoder: an encoder that can record a value's sensitivity returns a sub-encoder for a level, or nil to refuse it. protoMarshaller asks via ObjectEncoderFor and redacts only what the sink refuses, so an encoder that does not implement the interface behaves exactly as before -- proto_test.go covers that path unchanged. The assertion is on the encoder rather than a new marshaler interface because zapcore's Field.AddTo calls MarshalLogObject directly for InlineMarshalerType, so an encoder-side hook can never intercept a zap.Inline'd proto. Sensitivity propagates through the encoder, not the marshaller: a tagged encoder derives tagged children, so a sensitive message tags its whole subtree without protoMarshaller carrying any state for it. A SECRET field nested under a PII one still redacts, since the sink refuses SECRET at any depth. UnredactedProto is removed. Against a tagging sink it was identical to Proto, and its only remaining behaviour was exposing PII in the clear to sinks that cannot tag. maxLevel goes with it, as every constructor now leaves it at UNSPECIFIED. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
paulwe
force-pushed
the
logger/sensitivity-aware-encoder
branch
from
August 20, 2026 23:37
f41f88b to
c319b50
Compare
barrust
approved these changes
Aug 21, 2026
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.
Why
(logger.sensitivity)annotations are enforced by replacing the value with"<redacted>"(proto.go). That is right for console and JSON logs, but it also destroys data that a columnar sink could hold under a tag and filter per consumer. This is the protocol half of projecting sensitivity into the attribute wire format inbackend-common/observability.What
Adds
SensitiveObjectEncoder/SensitiveArrayEncoderinlogger/sensitive.go. An encoder that can record a value's sensitivity returns a sub-encoder for a level, ornilto refuse it.protoMarshallerasks viaObjectEncoderForand redacts only what the sink refuses.ObjectEncoderForalways returns nil and the redaction path runs as before.proto_test.goexercises that path throughzapcore.MapObjectEncoderand is unchanged apart from theUnredactedProtoremovals.zapcore'sField.AddTocallsMarshalLogObjectdirectly forInlineMarshalerType, so an encoder-side hook can never intercept azap.Inline'd proto.protoMarshallercarrying state for it. A SECRET field nested under a PII one still redacts, because the sink refuses SECRET at any depth.marshalSummaryrestructured, not reordered. Maps, lists and nested messagescontinuebefore the sensitivity decision. They disclose no field contents, and hoisting the decision above them would replace a non-disclosingfooCountwith a redaction.logger/sensitive_test.goadds a path-recording encoder double (a flat dot-joined recorder mirroring how the columnar sink flattens nesting) covering: PII stored with the real value rather than itsredact_formatoutput, SECRET always redacted, level inherited through a map, SECRET-under-PII, and summary mode tagging scalars while leaving counts untagged.Breaking
UnredactedProtois removed. Against a tagging sink it was identical toProto; its only remaining behaviour was exposing PII in the clear to sinks that cannot tag.maxLevelgoes with it, since every constructor now leaves it atUNSPECIFIED.cloudhas 30 call sites that need switching toProtobefore it can take this bump. For the columnar sink that is a no-op; for console output those fields move from cleartext to<redacted>, which is the safe direction.Note on rollout
A decoder that predates a sensitivity level cannot filter it — it ignores the tagged container tokens and so skips their key-depth adjustment. Every reader of an affected table has to be deployed before any writer starts tagging. That ordering is enforced by deploy sequencing, not by a flag.