Skip to content

tech-debt(redaction): llm_shared credential_redaction.redact_dict is a second recursive redactor that partially reveals keys #18205

Description

@mrveiss

Summary

There are two recursive mapping redactors. #18193 added autobot_shared/secret_redaction.py::redact_nested (the canonical module, BROAD policy, value-aware, keeps every non-secret field byte-identical). The older autobot-backend/llm_shared/credential_redaction.py::redact_dict also recurses, but differs in two ways that matter:

  • it also runs redact_string over non-credential strings, so non-secret values can change;
  • it partially reveals credential strings via redact_api_key (value[:4]...value[-4:]), where the canonical module uses a fixed-width placeholder.

redact_nested could not reuse redact_dict for GET /api/llm/config because of both differences. That leaves one concept with two implementations and different masking, which is what #17336 set out to remove.

Acceptance criteria

  • Every redact_dict caller is listed with what it needs: does it rely on content-scanning non-credential strings, or on the partial reveal?
  • redact_dict becomes a thin composition over redact_nested (plus redact_text content scanning where a caller genuinely needs it), or is removed with zero-caller evidence, so one recursive mapping redactor remains.
  • The partial reveal (value[:4]...value[-4:]) is either justified per caller in REDACTION_BOUNDARY.md or replaced by the fixed-width placeholder. Revealing 8 characters of a key is a disclosure, so this needs a stated reason.
  • The masked set at each caller does not shrink, proven with the tech-debt(redaction): two modules in autobot_shared both declare themselves the canonical redactor, and they disagree on 7 of 10 field names #17336 per-caller corpus method.

Related: #17336, #18193.

Activity

  1. mrveiss commented on Oct 11, 2026

    @mrveiss
    OwnerAuthor

    Closure evidence (merged in #18228, 1349f43). Verified against main by reading the merged code.

    • Callers and their needs. docs/developer/REDACTION_BOUNDARY.md:76-80 names replay_service._redact_sensitive and _redact_log_dict, safe_repr and CredentialRedactionFilter. They depend on the content scan, not on the partial reveal. The production call sites are autobot-backend/llc/services/replay_service.py:351,361,367 and autobot-backend/llm_shared/credential_redaction.py:123,158,160. The list is prose, not a table.
    • One recursive mapping redactor. autobot-backend/llm_shared/credential_redaction.py:105 makes redact_dict a composition: redact_nested(data, MatchPolicy.BROAD, exempt_counts=False, extra_scan=redact_string, mask_credential_urls=True). redact_nested is at autobot_shared/secret_redaction.py:387.
    • No partial reveal. redact_api_key returns REDACTED_PLACEHOLDER (credential_redaction.py:70). The disclosure fix is recorded at REDACTION_BOUNDARY.md:80-83.
    • The masked set does not shrink (tech-debt(redaction): two modules in autobot_shared both declare themselves the canonical redactor, and they disagree on 7 of 10 field names #17336 method). REDACTION_BOUNDARY.md:141 records redact_dict going from 2384 to 2449, with no shrink. Frozen-baseline tests: autobot-backend/llm_shared/tests/test_credential_redaction.py::test_no_name_leaves_redact_dicts_masked_set and autobot_shared/secret_redaction_nested_no_shrink_18205_test.py:36.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions