fix: prevent redaction false positives for token-like values - #8
Conversation
`known_token_prefix` matched on the marker alone, so `npm_` — the npm registry token prefix — claimed every variable npm and Bun inject into a script's environment. Piping `bun run <script>` output through `envctl redact` blanked npm_command, npm_config_*, npm_lifecycle_*, and npm_package_* wholesale, because each line was masked as a bare secret token before its `KEY=VALUE` shape was ever considered. Pair every marker with the body its issuer puts after it: a minimum run length and the alphabet that run stays inside. npm issues base62(uuid), 36 characters with no separator, so a 24-character base62 floor keeps real tokens masked while `npm_config_local_prefix` — separator at character 7 — falls out. The other markers gain the same guard, which also tightens the loose ones: `AKIA` now wants AWS's 16 upper-case characters, `SG.` a 24-character dotted body, and the vendor-neutral `sk-` fallback trades its total-length test for a body run. The run is measured from the marker, not required to reach the end of the value, so a token quoted in prose or embedded in a URL still masks.
Two more ways a config file read as credentials.
`is_jwt_shape` accepted any three dot-separated base64url segments over 20
characters, which is also what an ordinary identifier chain looks like:
`steps.publish.outputs` is three segments and 21 characters. A workflow
line carrying one inside `${{ }}` tripped the deep scan, and because the
whole `key: value` line is the value at that point, the line came back as
`outputs: <redacted>` with everything gone.
What a JWT adds to the shape is that its first two segments are a
base64url JSON object — the header and the claims set — so both open with
`ey`, the encoding of `{` and the byte after it. Requiring that is the
same anchor other scanners use, and every JWT in the suite already
carries it.
`id-token: write` masked because the key holds a TOKEN segment and
`write` was not a trivial value. It is a GitHub Actions permission
scope, so `read` and `write` join the trivial words next to `on`, `off`,
and `none` — a strong secret name no longer masks them.
This comment was marked as spam.
This comment was marked as spam.
📝 WalkthroughImprove secret and token detection accuracy. Require recognised token prefixes, valid character alphabets, and issuer-specific minimum lengths before redaction. Tighten JWT detection to require three segments with base64url-encoded JSON headers and claims. Preserve WalkthroughThe redaction engine now validates known token prefixes against minimum lengths and issuer-specific alphabets. It recognises Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 6 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (6 passed)
Comment |
This pull request improves the detection and redaction logic for secrets and tokens, making it both stricter and more accurate. It ensures that only valid tokens are redacted (not just by their prefixes), improves JWT shape validation, and expands the list of trivial values that should not be redacted. Several new and updated tests demonstrate the improved handling of common prefix collisions and edge cases.
Detection logic improvements:
npm_commandornpm_lifecycle_eventare no longer redacted, but real tokens likenpm_Pw6...are).ey) to be considered a JWT, preventing false positives on ordinary identifier chains [1] [2].Handling of trivial and non-secret values:
readandwrite, covering common permission scopes.id-token: writeand short numerics are not secrets and remain visible [1] [2].Test suite enhancements:
These changes make the redaction process more robust, reducing false positives and ensuring that only actual secrets are masked.