🛡 secret-guard — how it decides

A map of the mod's logic (hooks/register.tsx + hooks/rules.ts), for reading without reading the code. Source and install: github.com/hagaybar/claude-secret-guard

1 · Three moments the guard acts

Session start
Reads the environment once; keeps values of variables whose names look secret (…_KEY, …_TOKEN, …SECRET…, …PASSWORD…, …AUTH…), 8+ chars, not paths. In memory only. Registers /guard, sets the status line.
Before every tool call
Inspects what the call would do. Finds a risk → block (or flag in warn mode). No risk → run.
Before every row is stored
Scans text going into the transcript (tool output, replies, prompts). Replaces known values and token-shaped strings with [redacted:NAME].

2 · The decision for one tool call

flowchart TD
  A([Claude asks to run a tool]) --> B{Which tool?}

  B -->|Bash| C[Bash checks
secret-file read · env dump
printenv SECRET · $SECRET expansion
git add of secret file] C --> C2[Scan the command text
for known values + token shapes] C2 --> G{git / gh in
the command?} G -->|git add -A / .| G1[git status:
would a secret file be staged?] G -->|git commit| G2[git diff --cached
scan added lines + file names] G -->|git push| G3[git log of commits
not yet on any remote
scan added lines] G -->|gh pr / issue / release / gist| G4[Read --body-file / gist files
scan contents] G -->|none| F B -->|Read| R{Secret file?
.env · .bashrc · .aws · .ssh · secrets/ …} R -->|yes| X R -->|no| OK B -->|Write| W{Secret file?} W -->|yes| X W -->|no| W2[Scan new content
for known values + token shapes] B -->|Edit / NotebookEdit| E[Scan new text
structural edits of a secret file allowed,
carrying a value is not] B -->|Anything else
MCP · WebFetch · Agent …| M[Scan every text field
of the input] G1 --> F G2 --> F G3 --> F G4 --> F W2 --> F E --> F M --> F F{Any finding?} F -->|no| OK([✅ Call runs]) F -->|yes| MODE{Mode} MODE -->|block| X([⛔ Refused
Claude gets reason + safe alternative
toast · note on the call · banner · log]) MODE -->|warn| WN([⚠️ Runs, but flagged
toast · note · banner · log]) ERR[[The check itself crashed]] -.->|block mode| X ERR -.->|warn mode| OK classDef bad fill:#3a1414,stroke:#f85149,color:#fff; classDef good fill:#12301b,stroke:#3fb950,color:#fff; classDef warn fill:#3a2c0e,stroke:#d29922,color:#fff; class X bad; class OK good; class WN warn;
The failure path matters: in block mode a broken check holds the call back (fails closed). /guard warn is the escape hatch.

3 · The rules, in plain words

RuleStopsAllowed instead
secret-file-readcat/grep/sed/cp/… or Read on .bashrc, .env*, ~/.aws/*, .netrc, secrets/, .ssh/, .gnupg/, .psst/grep … | sed 's/=.*/=<redacted>/'; source, ls, test -f
secret-file-writeWrite over a secret filegive the user the line with a <placeholder>
env-dumpenv, printenv, set, export -p printing valuesenv | cut -d= -f1
printenv-secretprintenv API_KEY to the screenprintenv API_KEY | sha256sum | cut -c1-12
echo-secretecho $API_KEY[ -n "$API_KEY" ] && echo set, ${API_KEY:+set}, ${#API_KEY}
default-expansion${API_KEY:-x}, ${API_KEY:=x} (expand to the value)${API_KEY:+set}
secret-on-argvcurl -H "…$TOKEN" (visible in ps)printenv TOKEN | cmd --stdin, cmd <<< "$TOKEN"
git-add-secret-filestaging a secret file (explicitly or via -A/.)—
commit:* / push:*a commit or push whose added lines hold a value or token shape—
gh:*PR / issue / release / gist text holding one—
secret-valuea real value (from session start) anywhere in a command, file write or tool inputrefer to it by name
secret-shapea string that looks like a token, even if unknown<placeholder>

4 · Token shapes it recognises

AWS access key · GitHub token / fine-grained PAT · Anthropic key · OpenAI-style key · Slack token · Google API key · Ex Libris (Alma) API key · PEM private key header · JWT · password = "…"-style hard-coded credentials (checked, not redacted).

5 · How it shows itself

WhereWhat
Banner above the prompt“SECRET GUARD ON”, mode, counts (vars known · blocked · flagged · redacted), last event. [–] collapses it.
Status line🛡 guard on · N blocked
ToastOne per block / flag / redaction, with the reason (never the value).
On the tool call row“secret-guard blocked this call”
/guardstatus · log (last 20, all sessions) · block|warn · full|compact|off
/configmode, banner, secret-name regex, extra forbidden / allowed paths, redaction, git checks, value loading

6 · Known gaps (why it's a first draft)