Security
safe-chains is an allowlist-only command checker. It auto-approves bash commands that it can verify as safe. Any command not explicitly recognized is left for the human to approve.
What it prevents
Auto-approval of destructive, write, or state-changing commands. An agentic tool cannot use safe-chains to bypass permission prompts for rm, git push, sed -i, curl -X POST, or any command/flag combination not in the allowlist.
Auto-approval of credential reads. cat ~/.ssh/id_rsa, ~/.aws/credentials, ~/.npmrc, /etc/shadow, another user’s home, a raw device — refused, and a directory grant never widens to reach one. Also refused: any command that reads files it never names, since there is nothing to check the shield against — a recursive search, glob, traversal or recursive copy rooted above your project (grep -r secret ~, cp -r ~ ./backup).
Note the shape of this: ordinary reads outside your project are auto-approved (cat /etc/hosts, cat ~/.zshrc). The protection is about which file, not where it sits.
Auto-approval of writes outside your project. cp secret /etc/x and writes to system paths are not approved; writes stay within your project, its siblings and /tmp. See Files by location.
Auto-approval of writes to safe-chains’ own config. See Trusted directories.
Security properties
-
Allowlist-only: unrecognized commands are never approved.
-
Per-segment validation: commands with shell operators (
&&,|,;,&) are split into segments that are independently evaluated. All segments must return safe to approve the command.
Settings guardrails: when matching commands against your Claude Code settings patterns, segments containing >, <, backticks, or $() are never approved via settings, even if a pattern matches. This prevents Bash(./script *) from approving ./script > /etc/passwd.
Trusted configuration
A project’s .safe-chains.toml must be trusted before safe-chains honors it. See Custom Commands.
What it does not prevent
- Information disclosure inside your project: files in your project (and any trusted directories you grant) are readable, so
cat .envsends their contents to the model provider. safe-chains gates by location, not by content; it won’t stop a read inside a directory you’ve allowed. - Filesystem confinement: safe-chains is NOT a sandbox. If you manually approve a dangerous command, safe-chains will not stop it. If you replace a safe binary with an evil binary, safe-chains will let it run. It only reads command strings, statically. For real confinement, pair it with an OS sandbox or the harness’s own file controls. safe-chains’ job is to auto-approve the safe commands so the prompts you DO get are meaningful.
- Unrecognized commands: commands safe-chains doesn’t handle are passed through to the normal permission flow for your harness.
- Anything that is not a shell command. safe-chains is a hook on the shell tool. It sees the command string an agent is about to run and nothing else. An agent’s own file-editing tools, the ones that read and write files directly without going through a shell, never reach it. So the protections below hold for the shell and not beyond it: safe-chains will refuse
echo x > ~/.config/safe-chains.tomlin the shell, and it cannot see that same file being rewritten by a file-edit tool. Use your harness’s own file permissions for that; safe-chains is not a substitute for them. This is worth re-checking now and then, because harnesses add tools, and a new tool that runs commands some other way is a new surface safe-chains does not cover yet. - Chaining with broad approvals: if you add patterns like
Bash(bash *)to your Claude Code settings, safe-chains will match them per-segment without recursive validation, matching Claude Code’s own behavior. Those patterns apply only when the harness is Claude Code; another tool running safe-chains gets its own classification and nothing borrowed. See Cleaning up approved commands.
Best practices
Don’t run an agent from your home directory. safe-chains treats your working directory as the project, and files under it are read/write. From ~, a relative path like cat Documents/finances.csv is indistinguishable from a project file, so files throughout your home directory are exposed. Run agents from a project directory instead.
Grant narrowly. A trusted directory grant is a deliberate broad allow, the same broad access you get by running from a directory. Grant the specific directories you work in (~/projects), not all of ~. A grant only covers what it names, so a broad one can’t reach a credential store by accident — but a grant that names ~/.ssh or ~/.aws does reach it, and then it is treated like any other allowed directory: read = true approves reading a private key, and write = true approves writing authorized_keys and deleting the keys. Grant those only if that is what you meant.
Deny the folders you never want touched. On Claude Code, safe-chains only ever approves. It never denies, so a command it doesn’t recognize just falls through to Claude’s normal prompt. For a hard block on paths you never want touched, pair it with a second hook that denies them (Claude Code blocks the command if any hook denies it). A blunt backstop at ~/.claude/hooks/deny-paths.sh:
#!/bin/bash
cmd=$(jq -r '.tool_input.command // ""')
case "$cmd" in
*.ssh/*|*.aws/*|*.gnupg/*|*/.env|*/secrets/*)
jq -n '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:"protected path"}}' ;;
esac
Register it alongside safe-chains in ~/.claude/settings.json:
"hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [
{ "type": "command", "command": "bash ~/.claude/hooks/deny-paths.sh" },
{ "type": "command", "command": "safe-chains" }
] } ] }
It’s a string match, not a parser: a safety net for the obvious cases while safe-chains does the rigorous allow-listing.
Testing approach
Every command handler is covered by multiple layers of automated testing. Each handler includes explicit safe/denied test cases covering expected approvals and rejections. Every command has a spec suite. Every type of data definition in safe-chains has a test suite.
Reporting vulnerabilities
If you find a command that safe-chains approves but shouldn’t, please open an issue.
Contributing
Found a safe command safe-chains should support? Submit an issue.