Skip to content

Intelligent bash command permission gates for Claude Code using tree-sitter AST parsing

Notifications You must be signed in to change notification settings

camjac251/bash-gates

Repository files navigation

bash-gates

Intelligent permission gates for bash commands in Claude Code

CI Release Rust License: MIT

A Claude Code PreToolUse hook that analyzes bash commands using AST parsing and determines whether to allow, ask, or block based on potential impact.

Installation · Permission Gates · Security · Testing


Features

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

How It Works

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]
Loading

Why three hooks?

  • PreToolUse: Gates commands for main session, tracks "ask" decisions
  • PermissionRequest: Gates commands for subagents (where PreToolUse's allow is 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.

Settings.json Integration

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.

Accept Edits Mode

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 fix

Still 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

Modern CLI Hints

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-status

Approval Learning

When 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 local

Scopes:

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

Installation

Download Binary

# 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

Build from Source

# Requires Rust 1.85+
cargo build --release
# Binary: ./target/x86_64-unknown-linux-musl/release/bash-gates

Configure Claude Code

Use 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 json

Scopes:

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" decisions
  • PermissionRequest - 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}]
      }
    ]
  }
}

Permission Gates

Basics

~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 Issue Tracker

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

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)

GitHub CLI

Allow Ask Block
pr list, issue view, repo view, search, api (GET) pr create, pr merge, issue create, repo fork repo delete, auth logout

Git

Allow Ask Ask (warning)
status, log, diff, show, branch -a add, commit, push, pull, merge push --force, reset --hard, clean -fd

Shortcut CLI

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)

Cloud CLIs

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

Network

Allow Ask Block
curl (GET), wget --spider curl -X POST, wget, ssh, rsync nc -e (reverse shell)

Filesystem

Allow Ask Block
tar -tf, unzip -l rm, mv, cp, chmod, sed -i rm -rf /, rm -rf ~

Developer Tools

~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

Package Managers

npm, pnpm, yarn, pip, uv, cargo, go, bun, conda, poetry, pipx, mise

Allow Ask
list, show, test, build, dev install, add, remove, publish, run

System

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

Security Features

Pre-AST Security Checks

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 redirection

Compound Command Handling

Strictest 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)

Smart sudo Handling

sudo apt install vim          # ask - "sudo: Installing packages (apt)"
sudo systemctl restart nginx  # ask - "sudo: systemctl restart"

Testing

cargo test                        # Full suite
cargo test gates::git             # Specific gate
cargo test -- --nocapture         # With output

Manual Testing

# 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"}}

Architecture

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

Links

About

Intelligent bash command permission gates for Claude Code using tree-sitter AST parsing

Topics

Resources

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •  

Languages