Skip to content

fix: Escape attribute names reported in redactedAttributes - #505

Merged
jsonbailey merged 1 commit into
mainfrom
jb/sdk-2981/escaped-refs-for-redacted-attrs
Aug 26, 2026
Merged

fix: Escape attribute names reported in redactedAttributes#505
jsonbailey merged 1 commit into
mainfrom
jb/sdk-2981/escaped-refs-for-redacted-attrs

Conversation

@jsonbailey

@jsonbailey jsonbailey commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Requirements

  • I have added test coverage for new or changed functionality
  • I have followed the repository's pull request submission guidelines
  • I have validated my changes against all supported platform versions

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: any
  • events/feature events/multi-kind with anonymous context redacts attributes appropriately/type: any

The same fix has already landed in Ruby (launchdarkly/ruby-server-sdk#415) and Rust (launchdarkly/rust-server-sdk-evaluation#56). java-core and dotnet-core fail identically and still need it.

Describe the solution you've provided

_meta.redactedAttributes in 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:

expected: "/~1ssn"
actual:   "/ssn"

Two changes:

  • EventContextFormatter._check_whole_attr_private now converts the attribute name to a reference with AttributeRef.from_literal before adding it to the redacted list. This covers both the allAttributesPrivate/configured-private paths and the anonymous-context redaction path, which is why one change fixes all 6 subtests.
  • AttributeRef.from_literal built an invalid path: it escaped unconditionally and never prepended /, so /ssn became ~1ssn (which re-parses as a literal attribute named ~1ssn) and a~b became a~0b. It now matches Go's NewLiteralRef and Rust's Reference::from_literal_name — escape only when the name starts with /, converting ~ to ~0 and / to ~1 and prepending /. Every other name is already a valid reference and is left unchanged (name stays name, a/b~c stays a/b~c).

EventContextFormatter is shared by the sync and async event processors through event_processor_common, so this covers LDClient and AsyncLDClient alike.

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 lint clean.

Describe alternatives you've considered

Escaping only in the all_attributes_private branch — rejected, the same escaping is required wherever a whole attribute is redacted.

Escaping at the call site instead of fixing from_literal — rejected, from_literal produced a path that could not round-trip through from_path, which is a latent bug regardless of this caller.

Additional context

Nested redactions in _redact_json_value already reported AttributeRef.path, so they were unaffected. AttributeRef.path has 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.redactedAttributes instead of raw context attribute names. Whole-attribute redaction (all-private, configured private, and anonymous redaction) goes through AttributeRef.from_literal(...).path, so slash-prefixed names like /ssn are reported as /~1ssn rather than /ssn, which consumers would otherwise treat as a nested path.

AttributeRef.from_literal is 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 through from_path. Nested redaction in _redact_json_value was already using .path and is unchanged.

New unit tests cover escaped entries for all-private, anonymous redaction, and explicitly private slash-prefixed attributes, plus from_literal path expectations.

Reviewed by Cursor Bugbot for commit 26c0b4d. Bugbot is set up for automated code reviews on this repo. Configure here.

@jsonbailey
jsonbailey requested a review from a team as a code owner August 25, 2026 22:45
@jsonbailey
jsonbailey merged commit 90059cb into main Aug 26, 2026
26 of 27 checks passed
@jsonbailey
jsonbailey deleted the jb/sdk-2981/escaped-refs-for-redacted-attrs branch August 26, 2026 13:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants