Intelligent permission gates for bash commands in Claude Code
A Claude Code PreToolUse hook that analyzes bash commands using AST parsing and determines whether to allow, ask, or block based on potential impact.
| Feature | Description |
|---|---|
| Approval Learning | Tracks approved commands and saves patterns to settings.json via TUI or CLI |
| Settings Integration | Respects your settings.json allow/deny/ask rules - won't bypass your explicit permissions |
| Accept Edits Mode | Auto-allows file-editing commands (sd, prettier --write, etc.) when in acceptEdits mode |
| Modern CLI Hints | Suggests modern alternatives (bat, rg, fd, etc.) via additionalContext for Claude to learn |
| AST Parsing | Uses tree-sitter-bash for accurate command analysis |
| Compound Commands | Handles &&, ||, |, ; chains correctly |
| Security First | Catches pipe-to-shell, eval, command injection patterns |
| Unknown Protection | Unrecognized commands require approval |
| 300+ Commands | 12 specialized gates with comprehensive coverage |
| Fast | Static native binary, no interpreter overhead |
flowchart TD
CC[Claude Code] --> CMD[Bash Command]
subgraph PTU [PreToolUse Hook]
direction TB
PTU_CHECK[bash-gates check] --> PTU_DEC{Decision}
PTU_DEC -->|dangerous| PTU_DENY[deny]
PTU_DEC -->|risky| PTU_ASK[ask + track]
PTU_DEC -->|safe| PTU_CTX{Context?}
PTU_CTX -->|main session| PTU_ALLOW[allow ✓]
PTU_CTX -->|subagent| PTU_IGNORED[ignored by Claude]
end
CMD --> PTU
PTU_IGNORED --> INTERNAL[Claude internal checks]
INTERNAL -->|path outside cwd| PR_HOOK
subgraph PR_HOOK [PermissionRequest Hook]
direction TB
PR_CHECK[bash-gates re-check] --> PR_DEC{Decision}
PR_DEC -->|safe| PR_ALLOW[allow ✓]
PR_DEC -->|dangerous| PR_DENY[deny]
PR_DEC -->|risky| PR_PROMPT[show prompt]
end
PTU_ASK --> EXEC[Command Executes]
PR_PROMPT --> USER_APPROVE[User Approves] --> EXEC
subgraph POST [PostToolUse Hook]
direction TB
POST_CHECK[check tracking] --> POST_DEC{Tracked + Success?}
POST_DEC -->|yes| PENDING[add to pending queue]
POST_DEC -->|no| POST_SKIP[skip]
end
EXEC --> POST
PENDING --> REVIEW[bash-gates review]
REVIEW --> SETTINGS[settings.json]
Why three hooks?
- PreToolUse: Gates commands for main session, tracks "ask" decisions
- PermissionRequest: Gates commands for subagents (where PreToolUse's
allowis ignored) - PostToolUse: Detects successful execution, queues for permanent approval
Decision Priority: BLOCK > ASK > ALLOW > SKIP
| Decision | Effect |
|---|---|
| deny | Command blocked with reason |
| ask | User prompted for approval |
| allow | Auto-approved |
Unknown commands always require approval.
bash-gates reads your Claude Code settings from ~/.claude/settings.json and .claude/settings.json (project) to respect your explicit permission rules:
| settings.json | bash-gates | Result |
|---|---|---|
deny rule |
(any) | Defers to Claude Code (respects your deny) |
ask rule |
(any) | Defers to Claude Code (respects your ask) |
allow rule |
dangerous | deny (bash-gates still blocks dangerous) |
allow/none |
safe | allow |
| none | unknown | ask |
This ensures bash-gates won't accidentally bypass your explicit deny rules while still providing security against dangerous commands.
When Claude Code is in acceptEdits mode, bash-gates auto-allows file-editing commands:
# In acceptEdits mode - auto-allowed
sd 'old' 'new' file.txt # Text replacement
prettier --write src/ # Code formatting
ast-grep -p 'old' -r 'new' -U . # Code refactoring
sed -i 's/foo/bar/g' file.txt # In-place sed
black src/ # Python formatting
eslint --fix src/ # Linting with fixStill requires approval (even in acceptEdits):
- Package managers:
npm install,cargo add - Git operations:
git push,git commit - Deletions:
rm,mv - Blocked commands:
rm -rf /still denied
Requires Claude Code 1.0.20+
When Claude uses legacy commands, bash-gates suggests modern alternatives via additionalContext. This helps Claude learn better patterns over time without modifying the command.
# Claude runs: cat README.md
# bash-gates returns:
{
"hookSpecificOutput": {
"permissionDecision": "allow",
"additionalContext": "Tip: Use 'bat README.md' for syntax highlighting and line numbers (Markdown rendering)"
}
}| Legacy Command | Modern Alternative | Benefit |
|---|---|---|
cat, head, tail, less |
bat |
Syntax highlighting, line numbers |
grep -r |
rg |
Faster, respects .gitignore |
find |
fd |
Simpler syntax, faster |
ls -la |
eza |
Git integration, icons |
sed |
sd |
Simpler syntax |
du |
dust |
Visual tree view |
ps aux |
procs |
Better formatting |
diff |
delta |
Syntax-highlighted diffs |
cloc |
tokei |
Faster code stats |
Only suggests installed tools. Hints are cached (7-day TTL) to avoid repeated which calls.
# Refresh tool detection cache
bash-gates --refresh-tools
# Check which tools are detected
bash-gates --tools-statusWhen you approve commands (via Claude Code's permission prompt), bash-gates tracks them and lets you permanently save patterns to settings.json.
# After approving some commands, review pending approvals
bash-gates pending list
# Interactive TUI for batch approval
bash-gates review
# Or approve directly via CLI
bash-gates approve 'npm install*' -s local
bash-gates approve 'cargo*' -s user
# Manage existing rules
bash-gates rules list
bash-gates rules remove 'pattern' -s localScopes:
| Scope | File | Use case |
|---|---|---|
local |
.claude/settings.local.json |
Personal project overrides (not committed) |
user |
~/.claude/settings.json |
Global personal use |
project |
.claude/settings.json |
Share with team |
# Linux x64
curl -Lo ~/.local/bin/bash-gates \
https://github.com/camjac251/bash-gates/releases/latest/download/bash-gates-linux-amd64
chmod +x ~/.local/bin/bash-gates
# Linux ARM64
curl -Lo ~/.local/bin/bash-gates \
https://github.com/camjac251/bash-gates/releases/latest/download/bash-gates-linux-arm64
chmod +x ~/.local/bin/bash-gates
# macOS Apple Silicon
curl -Lo ~/.local/bin/bash-gates \
https://github.com/camjac251/bash-gates/releases/latest/download/bash-gates-darwin-arm64
chmod +x ~/.local/bin/bash-gates
# macOS Intel
curl -Lo ~/.local/bin/bash-gates \
https://github.com/camjac251/bash-gates/releases/latest/download/bash-gates-darwin-amd64
chmod +x ~/.local/bin/bash-gates# Requires Rust 1.85+
cargo build --release
# Binary: ./target/x86_64-unknown-linux-musl/release/bash-gatesUse the hooks subcommand to configure Claude Code:
# Install to user settings (recommended)
bash-gates hooks add -s user
# Install to project settings (shared with team)
bash-gates hooks add -s project
# Install to local project settings (not committed)
bash-gates hooks add -s local
# Preview changes without writing
bash-gates hooks add -s user --dry-run
# Check installation status
bash-gates hooks status
# Output hooks JSON for manual config
bash-gates hooks jsonScopes:
| Scope | File | Use case |
|---|---|---|
user |
~/.claude/settings.json |
Personal use (recommended) |
project |
.claude/settings.json |
Share with team |
local |
.claude/settings.local.json |
Personal project overrides |
All three hooks are installed:
PreToolUse- Gates commands for main session, tracks "ask" decisionsPermissionRequest- Gates commands for subagents (where PreToolUse's allow is ignored)PostToolUse- Detects successful execution, queues for permanent approval
Manual installation
Add to ~/.claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [{"type": "command", "command": "~/.local/bin/bash-gates", "timeout": 10}]
}
],
"PermissionRequest": [
{
"matcher": "Bash",
"hooks": [{"type": "command", "command": "~/.local/bin/bash-gates", "timeout": 10}]
}
],
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [{"type": "command", "command": "~/.local/bin/bash-gates", "timeout": 10}]
}
]
}
}~130+ safe read-only commands: echo, cat, ls, grep, rg, awk, sed (no -i), ps, whoami, date, jq, yq, bat, fd, tokei, hexdump, and more. Custom handlers for xargs (safe only with known-safe targets) and bash -c/sh -c (parses inner script).
Beads - Git-native issue tracking
| Allow | Ask |
|---|---|
list, show, ready, blocked, search, stats, doctor, dep tree, prime |
create, update, close, delete, sync, init, dep add, comments add |
mcp-cli - Claude Code's experimental token-efficient MCP interface
Instead of loading full MCP tool definitions into the system prompt, Claude discovers tools on-demand via mcp-cli and executes them through Bash. Enable with ENABLE_EXPERIMENTAL_MCP_CLI=true.
| Allow | Ask |
|---|---|
servers, tools, info, grep, resources, read, help |
call (invokes MCP tools) |
Pre-approve trusted servers in settings.json to avoid repeated prompts:
{
"permissions": {
"allow": ["mcp__perplexity", "mcp__context7__*"],
"deny": ["mcp__firecrawl__firecrawl_crawl"]
}
}Patterns: mcp__<server> (entire server), mcp__<server>__<tool> (specific tool), mcp__<server>__* (wildcard)
| Allow | Ask | Block |
|---|---|---|
pr list, issue view, repo view, search, api (GET) |
pr create, pr merge, issue create, repo fork |
repo delete, auth logout |
| Allow | Ask | Ask (warning) |
|---|---|---|
status, log, diff, show, branch -a |
add, commit, push, pull, merge |
push --force, reset --hard, clean -fd |
shortcut-cli - Community CLI for Shortcut
| Allow | Ask |
|---|---|
search, find, story (view), members, epics, workflows, projects, help |
create, install, story (with update flags), search --save, api (POST/PUT/DELETE) |
AWS, gcloud, terraform, kubectl, docker, podman, az, helm, pulumi
| Allow | Ask | Block |
|---|---|---|
describe-*, list-*, get, show, plan |
create, delete, apply, run, exec |
iam delete-user, delete ns kube-system |
| Allow | Ask | Block |
|---|---|---|
curl (GET), wget --spider |
curl -X POST, wget, ssh, rsync |
nc -e (reverse shell) |
| Allow | Ask | Block |
|---|---|---|
tar -tf, unzip -l |
rm, mv, cp, chmod, sed -i |
rm -rf /, rm -rf ~ |
~50+ tools with write-flag detection: jq, shellcheck, hadolint, vite, vitest, jest, tsc, esbuild, turbo, nx
| Safe by default | Ask with flags |
|---|---|
ast-grep, yq, semgrep, sad, prettier, eslint, biome, ruff, black, gofmt, rustfmt, golangci-lint |
-U, -i, --fix, --write, --commit, --autofix |
Always ask: sd (always writes), watchexec (runs commands), dos2unix |
npm, pnpm, yarn, pip, uv, cargo, go, bun, conda, poetry, pipx, mise
| Allow | Ask |
|---|---|
list, show, test, build, dev |
install, add, remove, publish, run |
Database CLIs: psql, mysql, sqlite3, mongosh, redis-cli Build tools: make, cmake, ninja, just, gradle, maven, bazel OS Package managers: apt, brew, pacman, nix, dnf, zypper, flatpak, snap Other: sudo, systemctl, crontab, kill
| Allow | Ask | Block |
|---|---|---|
psql -l, make test, sudo -l, apt search |
make deploy, sudo apt install |
shutdown, reboot, mkfs, dd, fdisk, iptables, passwd |
curl https://example.com | bash # ask - pipe to shell
eval "rm -rf /" # ask - arbitrary execution
source ~/.bashrc # ask - sourcing script
echo $(rm -rf /tmp/*) # ask - dangerous substitution
find . | xargs rm # ask - xargs to rm
echo "data" > /etc/passwd # ask - output redirectionStrictest decision wins:
git status && rm -rf / # deny (rm -rf / blocked)
git status && npm install # ask (npm install needs approval)
git status && git log # allow (both read-only)sudo apt install vim # ask - "sudo: Installing packages (apt)"
sudo systemctl restart nginx # ask - "sudo: systemctl restart"cargo test # Full suite
cargo test gates::git # Specific gate
cargo test -- --nocapture # With output# Allow
echo '{"tool_name":"Bash","tool_input":{"command":"git status"}}' | bash-gates
# → {"hookSpecificOutput":{"permissionDecision":"allow"}}
# Ask
echo '{"tool_name":"Bash","tool_input":{"command":"npm install"}}' | bash-gates
# → {"hookSpecificOutput":{"permissionDecision":"ask","permissionDecisionReason":"npm: Installing packages"}}
# Deny
echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /"}}' | bash-gates
# → {"hookSpecificOutput":{"permissionDecision":"deny"}}src/
├── main.rs # Entry point, CLI commands
├── models.rs # Types (HookInput, HookOutput, Decision)
├── parser.rs # tree-sitter-bash AST parsing
├── router.rs # Security checks + gate routing
├── settings.rs # settings.json parsing and pattern matching
├── hints.rs # Modern CLI hints (cat→bat, grep→rg, etc.)
├── tool_cache.rs # Tool availability cache for hints
├── mise.rs # Mise task file parsing and command extraction
├── package_json.rs # package.json script parsing and command extraction
├── tracking.rs # PreToolUse→PostToolUse correlation (5min TTL)
├── pending.rs # Pending approval queue (JSONL format)
├── patterns.rs # Pattern suggestion algorithm
├── post_tool_use.rs # PostToolUse handler
├── settings_writer.rs # Write rules to Claude settings files
├── tui/ # Interactive approval TUI (bash-gates review)
└── gates/ # 12 specialized permission gates
├── basics.rs # Safe commands (~130+)
├── beads.rs # Beads issue tracker (bd) - github.com/steveyegge/beads
├── mcp.rs # MCP CLI (mcp-cli) - Model Context Protocol
├── gh.rs # GitHub CLI
├── git.rs # Git
├── shortcut.rs # Shortcut CLI (short) - github.com/shortcut-cli/shortcut-cli
├── cloud.rs # AWS, gcloud, terraform, kubectl, docker, podman, az, helm, pulumi
├── network.rs # curl, wget, ssh, rsync, netcat, HTTPie
├── filesystem.rs # rm, mv, cp, chmod, tar, zip
├── devtools.rs # sd, ast-grep, yq, semgrep, biome, prettier, eslint, ruff, black
├── package_managers.rs # npm, pnpm, yarn, pip, uv, cargo, go, bun, conda, poetry, pipx, mise
└── system.rs # psql, mysql, make, sudo, systemctl, OS pkg managers, build tools