Skip to content

fix(secrets): redact only the offending span of a multi-line value, keeping entropy coverage - #1753

Merged
Shashankss1205 merged 3 commits into
CodeGraphContext:mainfrom
CSchneiderHav:fix/secret-scan-granularity
Oct 15, 2026
Merged

Shashankss1205 merged 3 commits into
CodeGraphContext:mainfrom
CSchneiderHav:fix/secret-scan-granularity

Conversation

@CSchneiderHav

@CSchneiderHav CSchneiderHav commented Oct 1, 2026 •

Copy link
Copy Markdown

Summary

is_likely_secret's entropy fallback flags a string of 16+ characters at 3.8 bits when it contains
one of ten hint words, and a whole code body clears that on its own. On one graph, 10,598 of 14,765
Class bodies and 19,353 of 98,706 Function bodies were stored as [REDACTED], which also
removed them from the full-text index. Across 1.72M lines of Java, 64–74% of 4 KB body chunks would
be blanked.

This makes redaction of multi-line source/docstring span-based instead of whole-value, while
keeping entropy coverage. Other properties and single-line values keep whole-value redaction.

What changes

  • Patterns redact the captured value, or the whole match when nothing is captured
    (AKIA…, ghp_…, Bearer …, JWTs, connection URIs). Patterns that span lines still match, so
    password =\n "hunter2" becomes password =\n "[REDACTED]". An unquoted captured value that is
    a plain identifier (accessToken = currentAccessTokenValue) is a variable reference and is
    skipped unless it also looks opaque.

  • Private keys are redacted from BEGIN through END (tolerant spacing, CRLF, blocks split over
    string concatenation), or to the end of the value when END was cut off by truncation.

  • Entropy is kept, using _shannon_entropy and the 3.8-bit threshold, but per candidate token
    instead of per body. Candidates are base64/base64url runs of 16+ characters (_MIN_ENTROPY_LEN)
    inside string literals, or outside them when not a plain identifier. A token must mix letters and
    digits, and either:

    • the name it is assigned to matches _KEY_HINT_RE — the existing hint, scoped to the name
      rather than the whole body, which is what stops private, token or authService elsewhere
      in a method from tripping it; or
    • it is not a UUID and switches between digit, lowercase and uppercase at least 0.3 times per
      character (a capital starting a word not counted). Random base62/base64 tokens of 20+
      characters score at or above that about 90% of the time; identifiers, paths and prose score
      below it.

    Hex cannot exceed 4 bits, so a named hex token is held to 3.0 bits, the detect-secrets/truffleHog
    hex limit. The string literal holding the token is redacted, so no fragment of the secret is left.

  • Three credential names without a name-anchored pattern get one: client_secret,
    refresh/id/session/sas_token, connection_string/shared_key/account_key.

scan_and_redact and is_likely_secret are unchanged; routing is a two-line change in
scan_props_and_redact to the new scan_body_and_redact.

Coverage compared with before

An unnamed opaque literal such as "8f3a1b2c9d4e5f60718293a4zQ" is now caught, which the scalar
rule did not catch. Remaining gaps: unnamed hex shorter than about 40 characters (also missed by the
scalar rule) and unquoted, identifier-shaped opaque values. Both are still caught when named.

Measured

78 of 1,719,720 lines of Java (54 of 16,597 files) get any redaction. They are credentials or
opaque fixtures (hashes, IDs), and in each case only the literal is replaced.

Tests

tests/unit/utils/test_secret_scanner_bodies.py covers unnamed opaque literals (Java, Python,
docstring), named key material and named versus unnamed hex, patterns inside bodies (AKIA,
client_secret, a password split across lines, several credentials in one body), PEM blocks (LF
and CRLF, doubled spacing, concatenated, missing END), and ordinary code using private, token,
accessKey and authService left untouched; source and docstring are parametrised to behave
the same. Regressions keep a multi-line value and a single-line source redacted whole.
tests/unit passes (1,534 passed, 8 skipped). It merges cleanly with #1760.


Disclosure: this change and its description were prepared with AI assistance (Claude Code) and
reviewed by a human before posting.

…he whole property

The entropy fallback flags any string of 16+ characters containing one of ten common words at 3.8 bits, which ordinary source clears: on one graph 10,598 of 14,765 Class bodies and 19,353 of 98,706 Function bodies were stored as [REDACTED], and a redacted body is also invisible to the full-text index. A multi-line value is now scanned per line. Regex coverage is unchanged: patterns are written with \\s, which matches a newline, so the whole value is still checked and redacted wholesale when a pattern spans lines, whatever the line pass found. Key blocks go BEGIN to END. Three credential names with no name-anchored pattern get one. Nothing replaces entropy for a body, which the pull request states as a reduction.
@vercel

vercel Bot commented Oct 1, 2026

Copy link
Copy Markdown

Someone is attempting to deploy a commit to the shashankss1205's projects Team on Vercel.

A member of the Team first needs to authorize it.

@Shashankss1205 Shashankss1205 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not merge as-is: explicitly removes entropy detection for multiline content and gives up opaque credential coverage. Agree the security tradeoff or use span-based redaction, then test multiline credentials and PEMs. Independent #1754 identity fix is prepared.

The independent identity-redaction defect now has a focused fix in #1760. That change keeps existing content detection and does not decide the multiline coverage reduction in this PR.

@CSchneiderHav CSchneiderHav changed the title fix(secrets): redact the offending lines of a multi-line value, not the whole property fix(secrets): redact only the offending span of a multi-line value, keeping entropy coverage Oct 9, 2026
@CSchneiderHav

Copy link
Copy Markdown
Author

Fair point, I've reworked it to redact only the matched span instead of dropping entropy. In multi-line source/docstring, pattern hits replace only the captured value, and PEM blocks are replaced from BEGIN to END (CRLF, doubled spacing, truncated). The entropy check still uses _shannon_entropy and the 3.8-bit threshold, but it runs on candidate tokens instead of the whole body. The hint word comes from the name the token is assigned to, and unnamed tokens get a shape test, so opaque credentials with no telltale name are now caught too. Over 1.7M lines of Java, 78 lines get any redaction, compared with 64–74% of bodies blanked before. The new tests cover multi-line credentials, PEMs and ordinary code using private/token/authService. It merges cleanly with #1760.

@Shashankss1205 Shashankss1205 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed the new span-based implementation. Multiline source/docstrings retain non-secret code; captured values and complete/truncated PEM blocks are redacted, token-level entropy coverage remains, and merging with #1760 preserves graph identifiers. All 1,652 combined unit tests pass (8 skips), including the new body-redaction regressions; strict parity and platform E2E pass on #1766. This addresses the earlier whole-body/entropy review concerns. Included with original commits in #1766.

@Shashankss1205
Shashankss1205 merged commit 80f485c into CodeGraphContext:main Oct 15, 2026
1 check failed
@github-project-automation github-project-automation Bot moved this from Backlog tasks to Done in CGC Progress Board Oct 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants