fix: Escape attribute names reported in redactedAttributes - #505
Merged
Conversation
keelerm84
approved these changes
Aug 26, 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.
Requirements
Related issues
Surfaced by contract test harness v3.2.0-alpha.9 (the pin bump in #492). These 6 subtests fail against the newer harness:
events/context properties/single-kind, allAttributesPrivate, slash-prefixed attribute name(debug, identify, index-from-evaluation, index-from-custom-event)events/feature events/single-kind anonymous context redacts all attributes/type: anyevents/feature events/multi-kind with anonymous context redacts attributes appropriately/type: anyThe same fix has already landed in Ruby (launchdarkly/ruby-server-sdk#415) and Rust (launchdarkly/rust-server-sdk-evaluation#56).
java-coreanddotnet-corefail identically and still need it.Describe the solution you've provided
_meta.redactedAttributesin event payloads is a list of attribute references, not raw attribute names. When a context has an attribute whose name begins with/(e.g./ssn), the SDK emitted the raw name, which a consumer parses as a path expression pointing at a nested property rather than the top-level attribute:Two changes:
EventContextFormatter._check_whole_attr_privatenow converts the attribute name to a reference withAttributeRef.from_literalbefore adding it to the redacted list. This covers both theallAttributesPrivate/configured-private paths and the anonymous-context redaction path, which is why one change fixes all 6 subtests.AttributeRef.from_literalbuilt an invalid path: it escaped unconditionally and never prepended/, so/ssnbecame~1ssn(which re-parses as a literal attribute named~1ssn) anda~bbecamea~0b. It now matches Go'sNewLiteralRefand Rust'sReference::from_literal_name— escape only when the name starts with/, converting~to~0and/to~1and prepending/. Every other name is already a valid reference and is left unchanged (namestaysname,a/b~cstaysa/b~c).EventContextFormatteris shared by the sync and async event processors throughevent_processor_common, so this coversLDClientandAsyncLDClientalike.Verification
Contract test service against released harness v3.2.0-alpha.9: 4771 total, 15 skipped, all ran passed (6 failures before). Against v2.41.0: 4766 total, 17 skipped, all ran passed — no regression on the FDv1 job. Unit suite: 1427 passed.
make lintclean.Describe alternatives you've considered
Escaping only in the
all_attributes_privatebranch — rejected, the same escaping is required wherever a whole attribute is redacted.Escaping at the call site instead of fixing
from_literal— rejected,from_literalproduced a path that could not round-trip throughfrom_path, which is a latent bug regardless of this caller.Additional context
Nested redactions in
_redact_json_valuealready reportedAttributeRef.path, so they were unaffected.AttributeRef.pathhas exactly one consumer in the SDK (the redaction list), so the corrected raw path does not change evaluation behavior.Note
Overview
Event payloads now list proper attribute references in
_meta.redactedAttributesinstead of raw context attribute names. Whole-attribute redaction (all-private, configured private, and anonymous redaction) goes throughAttributeRef.from_literal(...).path, so slash-prefixed names like/ssnare reported as/~1ssnrather than/ssn, which consumers would otherwise treat as a nested path.AttributeRef.from_literalis aligned with other LaunchDarkly SDKs: names without a leading/are left unchanged; names that start with/get/plus escape sequences (~→~0,/→~1) so the reference round-trips throughfrom_path. Nested redaction in_redact_json_valuewas already using.pathand is unchanged.New unit tests cover escaped entries for all-private, anonymous redaction, and explicitly private slash-prefixed attributes, plus
from_literalpath expectations.Reviewed by Cursor Bugbot for commit 26c0b4d. Bugbot is set up for automated code reviews on this repo. Configure here.