🛡 secret-guard — how it decides
First draft. Pattern-based and best effort: it catches the shapes it knows, not every possible leak.
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
| Rule | Stops | Allowed instead |
secret-file-read | cat/grep/sed/cp/… or Read on .bashrc, .env*, ~/.aws/*, .netrc, secrets/, .ssh/, .gnupg/, .psst/ | grep … | sed 's/=.*/=<redacted>/'; source, ls, test -f |
secret-file-write | Write over a secret file | give the user the line with a <placeholder> |
env-dump | env, printenv, set, export -p printing values | env | cut -d= -f1 |
printenv-secret | printenv API_KEY to the screen | printenv API_KEY | sha256sum | cut -c1-12 |
echo-secret | echo $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-argv | curl -H "…$TOKEN" (visible in ps) | printenv TOKEN | cmd --stdin, cmd <<< "$TOKEN" |
git-add-secret-file | staging 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-value | a real value (from session start) anywhere in a command, file write or tool input | refer to it by name |
secret-shape | a 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
| Where | What |
| Banner above the prompt | “SECRET GUARD ON”, mode, counts (vars known · blocked · flagged · redacted), last event. [–] collapses it. |
| Status line | 🛡 guard on · N blocked |
| Toast | One per block / flag / redaction, with the reason (never the value). |
| On the tool call row | “secret-guard blocked this call” |
/guard | status · log (last 20, all sessions) · block|warn · full|compact|off |
/config | mode, banner, secret-name regex, extra forbidden / allowed paths, redaction, git checks, value loading |
6 · Known gaps (why it's a first draft)
- Commands in a form the patterns don't anticipate (aliases, scripts,
eval, odd quoting) can pass.
- Only variables present at session start, with secret-looking names, are known by value.
- Git checks run in the session folder (or a leading
cd / git -C); a commit chained after git add in one command is checked against what was staged before it.
- Redaction can't reach anything already stored, committed or pushed.