Skip to content

fix(privacy): redact flagged object keys in place - #4515

Merged
ScriptedAlchemy merged 4 commits into
fix/lcm-retry-transient-summarizer-failuresfrom
fix/privacy-redact-keys-in-place
Oct 25, 2026
Merged

ScriptedAlchemy merged 4 commits into
fix/lcm-retry-transient-summarizer-failuresfrom
fix/privacy-redact-keys-in-place

Conversation

@ScriptedAlchemy

@ScriptedAlchemy ScriptedAlchemy commented Oct 24, 2026 •

Copy link
Copy Markdown
Owner

Stack order (merge first → last): #4473 → #4478 → #4521 → #4526 → #4515 → #4510 → #4516 → #4524 → #4489 → #4488 → #4485

This PR is layer 5/11, based on #4526. Next: #4510.

Merge bottom to top using merge commits. After each parent merges, retarget its child to master before merging the child; this preserves the ancestry already integrated and avoids reintroducing conflicts.

Stacked on #4512. This branch is based on revert/privacy-compound-identifier-exemption (the revert of #4490), which has not merged yet. Until it merges, this PR's diff also shows that revert commit. The two commits that belong to this PR are test(privacy): expect high-entropy object keys redacted in place and fix(privacy): redact flagged object keys in place.

Problem

A JSON or TOML file whose object key trips the credential or entropy detector is withheld from the index whole (privacy sanitizer quarantined credential-bearing keys). Long identifiers used as keys score above the 4.2 bits/char entropy floor, so ordinary files never reach the index. Indexing this repo with an isolated daemon built from the base branch withholds exactly two files for this reason:

- "benchmark_data/index-bench/clone-envelope-20260915-fingerprint-staging.json" (privacy withheld: privacy sanitizer quarantined credential-bearing keys)
- "dashboard/codegen/schemas/dashboard-contracts.schema.json" (privacy withheld: privacy sanitizer quarantined credential-bearing keys)

The flagged keys are 8 $defs type names (WorkDuplicateAdjudicationAppendOutcomeV1 and similar) and one benchmark target name (clone_peak_rss_added_at_most_1_gib_for_39582_symbols). #4451 item 3 reports the same class.

Root cause

collect_tree_fields in structured_text.rs ran the detectors on every key, but it could only record a quarantine finding, because the walk did not keep the key's location. A flagged key therefore withheld the whole document, even though the JSON (jsonc_parser) and TOML (DeTable) parsers already report each key's byte range.

#4490 tried to exempt "compound identifiers" from detection by shape. That was reverted, because no shape rule separates identifiers from passphrase secrets.

Fix

Detection is unchanged and no token is exempted. Only the response to a flagged key changes.

  • DocumentNode::Object now carries a DocumentKey { name, span }. JSON keys get the span of the body inside the quotes. TOML keys get the span of the whole token, quotes included.
  • A flagged key with a span is replaced in place by TraceDecay-redacted-key-N. N is unique within the object, numbered in document order, and skips any sibling's literal name. The placeholder is a valid bare TOML key and needs no JSON escaping. Every scalar under the key is redacted with the existing sensitive-field marker. Nested keys stay, and each one is still scanned. Everything else in the document stays byte-exact, so the file is indexed.
  • The key's detector findings are recorded as Redacted, and the receipt is Redacted/Secret.
  • Symbols derived from the key, such as the JSON-key-as-const extraction, come from the sanitized bytes. They are named by the placeholder and never contain the key.

The document is still quarantined (CredentialKeyQuarantine) when in-place redaction is impossible:

  1. The format is parsed without key spans. In practice that is YAML, through serde_yaml_ng.
  2. The rewritten text no longer parses as its own format. Example: a TOML table named in two headers ([secret] then [secret.child]). The parser reports one key span, and the raw-scan redaction of the second header breaks the syntax.
  3. The JSON value route (sanitize_structured_payload / redact_sensitive_values, which serves LCM JSON payloads, memory facts, and observation records) is unchanged and still quarantines flagged keys.

Long Rust type names in source text are still redacted by the raw scan. That false positive is known, and this PR does not touch it.

Verification

Focused tests (bazel test //crates/tracedecay-privacy/...): 150 passed. The new structured_text_tests::key_redaction suite has 10 tests. Before the fix, 7 failed (the documents were quarantined) and 3 passed (the guards). After the fix, all 10 pass. The suite covers:

  • The passphrase samples violin-pocket-jungle-quartz-island-778, RocketButtonStapleBridgeYellowOrange90, and MySuperSecretPasswordForProduction2024Admin. As keys (including {"api_keys": {"<secret>": "admin-user"}}) they become placeholders and the value under them is redacted. As values, in URL paths, and in free text after let/use/type, they stay redacted.
  • Placeholder uniqueness, including a sibling that already has the placeholder's name.
  • Escaped JSON keys, which are replaced byte-exactly.
  • Bare and quoted TOML keys.
  • The fixed point: sanitizing the output again changes nothing.
  • The YAML quarantine and the TOML two-header quarantine.

Two tests asserted the old TOML key-quarantine behavior. They are replaced by credential_keys_redact_in_place_on_code_source_and_lcm_text_routes. Clippy (--config=clippy //crates/tracedecay-privacy/...) and rustfmt are clean.

Real surface: an isolated daemon (TRACEDECAY_DATA_DIR and HOME in a temp dir) ran the built binary and indexed this worktree. A scratch privacy-probe.json with passphrase keys was added for the run and deleted afterwards.

dashboard/codegen/schemas/dashboard-contracts.schema.json (7 symbols)
clone-envelope-20260915-fingerprint-staging.json (11 symbols)
worktree sources not indexed: 1  (mockups/.../snapshot.json: bounded scan limit, unrelated)

privacy-probe.json symbols:
  TraceDecay-redacted-key-1 | "TraceDecay-redacted-key-1": {"role": "TraceDecay-redacted-sensitive-field"}
  api_keys | "api_keys": {"TraceDecay-redacted-key-1": "TraceDecay-redacted-sensitive-field"}
  region | "region": "us-east"

A byte grep of the isolated data dir finds 0 occurrences of each of the 9 flagged keys and of both probe passphrases.

//crates/tracedecay-code-index-runtime:unit_test has one failure, git_transactions::native::tests::unbounded_snapshot_captures_a_listing_beyond_the_default_read_limit (Err(NeedsInspection)). It fails the same way on the base branch without this change, and that module does not use the sanitizer.

🤖 Generated with Claude Code

@changeset-bot

changeset-bot Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 4d6cd3e

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

ScriptedAlchemy and others added 3 commits October 25, 2026 01:13
JSON and TOML documents whose keys read as high-entropy tokens are
withheld whole today. Pin the target behavior: the key becomes a
placeholder unique within its object, the values under it are redacted,
and passphrase-shaped secrets stay redacted in every position.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A JSON or TOML object key that trips a credential or entropy detector
withheld the whole document, because the sanitizer had no way to rewrite
a key. Long identifiers used as keys (schema $defs type names, benchmark
target names, path keys) score above the entropy floor, so files such as
dashboard-contracts.schema.json were never indexed.

The span-reporting JSON and TOML parsers already give each key's byte
range. Replace a flagged key with a placeholder unique within its object
(TraceDecay-redacted-key-N), redact every scalar under it, and keep the
rest of the document byte-exact. Detection is unchanged: no token is
exempted. A document is still withheld when the key has no span (YAML)
or the rewritten text no longer parses as its format.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ScriptedAlchemy
ScriptedAlchemy force-pushed the fix/privacy-redact-keys-in-place branch from 12db5fa to 08f86e7 Compare October 25, 2026 01:13
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.

1 participant