Stop secret-shape detection from swallowing file paths - #85
Merged
Conversation
The `secret-json-value` pattern matches `KEY\n VALUE` with no separator required between the two, so any line ending in a token containing a secret-ish word (`token`, `secret`, `auth`, `password`) not at position 0 caused the entire next whitespace-delimited token to be registered as a secret. In practice that ate file paths out of tool output: `git diff --name-only`, `ls`, `find`, and grep listings in any repo with an `*Auth*`/`*Token*`-named file lost roughly one path per 150. Measured across 10 repos, 27 of 4,265 tracked paths were affected. The loss is silent and position-dependent — it turns on the preceding line, so the same path survives one listing and vanishes from the next — and once registered the value stays redacted for the proxy's lifetime. Reject values shaped like multi-segment filesystem paths, tolerating a trailing grep/ripgrep locator (`src/defaults.ts:12:9`). The check is ordered after the credential-URL and known-shape checks so those still win, and a segment that looks like credential material (>=20 chars, no separator, mixing letter cases with digits) disqualifies the whole value so a slash-containing secret is not mistaken for a path. Length alone does not work as the discriminator: capping segment length either keeps eating real filenames or drops genuine credentials. This is the text-surface sibling of the JSON-key corruption fixed in #83. That fix separates leaves in the joined detection text; this one fires on genuine multi-line string leaves, so it survived. Measured 27 -> 0 false positives across the same 4,265 files. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Merged
This was referenced Jul 27, 2026
steflsd
added a commit
that referenced
this pull request
Jul 27, 2026
The detector docs described what secret-shapes catches but not what it deliberately does not, which invites the assumption that enabling it covers any secret in any config an agent reads. Add a "Known coverage limits" section covering the three key/value pairing positions where an opaque value is missed (secret-ish word at the start of the key, decoration between separator and value, flag forms with no separator), each with the reason it is not simply widened. Also document the path-shaped-value rejection added in #85, so the behaviour reads as intentional rather than as a detector miss. Cross-reference from the threat model's "Intentionally not covered" list, and restate that none of this affects the exact-match promise for registered values. Co-authored-by: Stefan Lesicnik <steflsd@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
@coderabbitai ignore
The
secret-json-valuepattern matchesKEY\n VALUEwith no separator required between key and value. So any line ending in a token containing a secret-ish word (token,secret,auth,password) — as long as the word isn't at position 0 — caused the entire next whitespace-delimited token to be registered as a secret.In practice that ate file paths out of tool output.
Impact
Any
git diff --name-only,ls,find, or grep listing in a repo containing an*Auth*/*Token*-named file lost paths. Measured across 10 repos: 27 of 4,265 tracked paths (~0.6%).Two properties made it hard to spot:
FICTA_<hex>surrogate, not dropped, so a listing looks well-formed.convex/lib/rotateToken→ next pathpackages/token(word at position 0)apps/web/hooks/useAuth.ts→ next pathpackages/authStore(same reason)(registered-secret→path:12:authoauth→ next pathNo leak, and no request failure — restore-into-tools puts the real path back into later tool args. The cost is that an agent sees an opaque token where a path should be and reasons about it. This was found because a session flagged one as evidence that "something is injecting into git config" and spent a turn on a phantom bug.
Fix
Reject values shaped like multi-segment filesystem paths, tolerating a trailing grep/ripgrep locator (
src/defaults.ts:12:9). Ordered after the credential-URL and known-shape checks so those still win.A segment that looks like credential material — ≥20 chars, no
./-/_, mixing letter cases with digits — disqualifies the whole value, so a slash-containing secret isn't mistaken for a path.Length alone does not work as the discriminator. A 32-char segment cap still ate
src/routes/api/v1/$.tsand a 33-char filename; widening to 64 then dropped a genuine credential (Xk9s…uI4o/S, 40-char segment). Requiring an unbroken mixed-case-plus-digit run keeps content-hashed assets likeapp.4f3a2b1c9d8e.json the path side, since they carry separators.Relationship to #83
This is the text-surface sibling of the JSON-key corruption fixed there. That fix separates leaves in the
\n-joined detection text; this one fires on genuine multi-line string leaves, so it survived. Verified present in published 0.2.1 by running the repro against the installed tarball — which is what shimmed sessions actually execute.Verification
path:line:locator andsrc/routes/api/v1/$.ts), plus a true-positive guard that base64 and slash-containing credentials still pair with a bareAPI_TOKENkey — that second test is what caught the 64-char-cap mistake.pnpm check(biome + engine-boundary + typecheck + tests): 51 files, 562 tests, all pass.🤖 Generated with Claude Code