Skip to content

fix(secret-guard): kill 8 rule false positives, drop S-62 code from banner#96

Merged
tieubao merged 1 commit into
mainfrom
fix/secret-guard-fp-sweep
May 12, 2026
Merged

fix(secret-guard): kill 8 rule false positives, drop S-62 code from banner#96
tieubao merged 1 commit into
mainfrom
fix/secret-guard-fp-sweep

Conversation

@tieubao
Copy link
Copy Markdown
Member

@tieubao tieubao commented May 12, 2026

Summary

Sweep of rule-level false positives surfaced while running Cloudflare API work yesterday and today. Same family of bug across multiple rules: flat regex across the whole CMD with no segment-awareness, plus substring matches without terminators. All were hitting normal API/devops workflows.

  • B3 (echo-of-secret): capture-form-aware ($() and ` strips) + quote-aware segment split on ;/&&/||. Stops false-firing on $(printf '%s' "$T" | wc -c) and on echo "non-secret" && curl -H "Bearer $TOKEN" > /file.
  • B5 (cat-class read): same segment split; SEPCLASS terminator (excludes .) so .netrc.tmpl / credentials.tmpl read straight through. Bare .pem dropped from the regex; /etc/ssl/private/ and /etc/pki/tls/private/ added explicitly so canonical private-key dirs still block.
  • B6 (heredoc-expands-secret): new extract_unquoted_heredoc_bodies awk helper inspects ONLY the body of unquoted-marker heredocs. Quoted-marker bodies and out-of-heredoc text no longer trip the rule.
  • B8 (interpreter -c reads secret): three signals (interpreter form, env-access syntax, secret name) now required IN THE SAME SEGMENT, not aggregated across &&.
  • B2d (decryption to terminal): terminators after --decrypt / -d so gpg --decrypt-files and openssl enc -des-cbc don't substring-match the decrypt verbs.
  • R1 (Read tool path): drops bare *.pem (couldn't tell public from private); adds */etc/ssl/private/* and */etc/pki/tls/private/* explicitly.

Banner: stripped (S-62/<code>) from the user-facing block message and replaced with a short per-rule label (e.g. BLOCKED: echo of a secret variable). Rule code still lands in ~/.cache/claude-secret-guard.log for grep. Banner now starts on its own line.

Two new shell helpers (split_quote_aware, extract_unquoted_heredoc_bodies) consolidate the awk used by multiple rules.

Test plan

  • Spec self-test: tests/secret-guard.sh — 115/115 pass (tests 120/122/123 updated to match the new banner contract: short-label in banner, rule code in audit log only)
  • End-to-end Cloudflare replay: both originally-blocked commands (zones?name=han.ws DNS list, workers GraphQL with unquoted JSON heredoc) now run through to a real API response
  • Manual audit sweep (22 cases): 10 false-positive shapes now allow, 12 real-leak shapes still block (including /etc/ssl/private/server.pem)
  • On-apply run_onchange_after_secret-guard-test.sh.tmpl will re-run the spec test on the next chezmoi apply and surface any regression via the apply summary

🤖 Generated with Claude Code

…anner

Same family of bug across multiple rules: flat regex across the whole
CMD with no segment-awareness, and substring matches without
terminators. Tightened all of them:

- B3: capture-form-aware ($() and `` strips) + quote-aware segment
  split on ;/&&/||. Stops false-firing on $(printf '%s' "$T" | wc -c)
  and on echo-in-one-segment + safe -H "Bearer $TOKEN"-in-another.
- B5: same segment split; SEPCLASS terminator (excludes `.`) so
  .netrc.tmpl / credentials.tmpl read straight through. Bare .pem
  dropped from the regex; /etc/ssl/private and /etc/pki/tls/private
  added explicitly so canonical private-key dirs still block.
- B6: inspects ONLY the body of unquoted-marker heredocs (new
  extract_unquoted_heredoc_bodies awk helper). Quoted-marker bodies
  and out-of-heredoc text no longer trip the rule.
- B8: three signals (interpreter -c, env-access, secret-name) now
  required IN THE SAME SEGMENT, not aggregated across &&.
- B2d: terminators after `--decrypt` / `-d` so `gpg --decrypt-files`
  and `openssl enc -des-cbc` don't substring-match decrypt verbs.
- R1: drops bare *.pem; adds */etc/ssl/private/* and */etc/pki/tls/
  private/* (canonical private-key dirs).

Banner: stripped `(S-62/<code>)` from the user-facing block message
and replaced with a short per-rule label, e.g. `BLOCKED: echo of a
secret variable`. Rule code still lands in `~/.cache/claude-secret-
guard.log` for grep. Banner now also starts on its own line (prior
output concatenated onto the `[~/.claude/hooks/...]:` prefix).

Two new shell helpers (split_quote_aware, extract_unquoted_heredoc_
bodies) consolidate the awk used by multiple rules.

Verified end-to-end: both originally-blocked Cloudflare commands
(zones?name=han.ws and workers GraphQL with unquoted JSON heredoc)
now run through to a real API response. Spec self-test: 115/115.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@tieubao tieubao merged commit b392823 into main May 12, 2026
2 checks passed
@tieubao tieubao deleted the fix/secret-guard-fp-sweep branch May 12, 2026 02:01
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