Skip to content

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
Claude Code Plugin Install as a plugin with the /bash-gates:review skill for interactive approval management
300+ Commands 13 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

PermissionRequest metadata like blocked_path and decision_reason is optional in Claude Code payloads. bash-gates treats those fields as best-effort context, not required inputs.

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.

Settings file priority (highest wins):

Priority Location Description
1 (highest) /etc/claude-code/managed-settings.json Enterprise managed
2 .claude/settings.local.json Local project (not committed)
3 .claude/settings.json Shared project (committed)
4 (lowest) ~/.claude/settings.json User settings

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 When triggered
cat, head, tail, less bat Always (tail -f excluded)
grep (code patterns) sg AST-aware code search
grep (text/log/config) rg Any grep usage
find fd Always
ls eza With -l or -a flags
sed sd Substitution patterns (s/.../.../)
awk choose Field extraction (print $)
du dust Always
ps procs With aux, -e, -A flags
curl, wget xh JSON APIs or verbose mode
diff delta Two-file comparisons
xxd, hexdump hexyl Always
cloc tokei Always
tree eza -T Always
man tldr Always
wc -l rg -c Line counting

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

Pattern suggestions: Patterns go from most specific to most broad:

Example Command Suggested Patterns
npm install lodash npm install lodash, npm install*, npm*
cargo test cargo test*, cargo*
aws ec2 describe-instances aws ec2 describe*, aws ec2*

TUI keybindings (bash-gates review):

Key Action
Up/Down or j/k Navigate commands
Tab or 1-9 Select pattern
s Cycle scope (Local -> User -> Project)
Enter or a Approve with selected pattern
d or Delete Skip (remove from pending)
q or Esc Quit

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

Claude Code Plugin (Optional)

bash-gates ships as a Claude Code plugin with the /bash-gates:review skill for interactive approval management. The plugin provides the skill only -- hook installation is handled by the binary (see Configure Claude Code above).

Prerequisites: The bash-gates binary must be installed and hooks configured before using the plugin.

Install from marketplace:

# In Claude Code, add the marketplace
/plugin marketplace add camjac251/bash-gates

# Install the plugin
/plugin install bash-gates@camjac251-bash-gates

Install from local clone:

# Launch Claude Code with the plugin loaded
claude --plugin-dir /path/to/bash-gates

Using the review skill:

# Review all pending approvals
/bash-gates:review

# Review only current project
/bash-gates:review --project

The skill lists commands you've been manually approving, shows counts and suggested patterns, and lets you multi-select which to make permanent at your chosen scope (local, project, or user).

Step What happens Permission
List pending approvals bash-gates pending list Auto-approved (read-only)
Show current rules bash-gates rules list Auto-approved (read-only)
Approve a pattern bash-gates approve '<pattern>' -s <scope> Requires your confirmation

Permission Gates

Bash Gates (Self)

bash-gates recognizes its own CLI commands:

Allow Ask
pending list, pending count, rules list, hooks status, --help, --version, --tools-status, --export-toml approve, rules remove, pending clear, hooks add, review, --refresh-tools

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
sd (pipe mode safe, ask with file args), always ask: 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

Comments are stripped before checking (quote-aware, respects bash word-boundary rules for #) so patterns inside comments don't trigger false positives.

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 (15min TTL)
├── pending.rs           # Pending approval queue (JSONL format)
├── patterns.rs          # Pattern suggestion algorithm
├── post_tool_use.rs     # PostToolUse handler
├── permission_request.rs # PermissionRequest hook handler
├── settings_writer.rs   # Write rules to Claude settings files
├── toml_export.rs       # TOML policy export for Gemini CLI
├── generated/           # Auto-generated by build.rs (DO NOT EDIT)
│   ├── rules.rs         # Rust gate functions from rules/*.toml
│   └── toml_policy.rs   # Gemini CLI TOML policy string
├── tui/                 # Interactive approval TUI (bash-gates review)
└── gates/               # 13 specialized permission gates
    ├── mod.rs           # Gate registry (ordered by priority)
    ├── helpers.rs       # Common gate helper functions
    ├── bash_gates.rs    # bash-gates CLI itself
    ├── 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

Gemini CLI Integration

Gemini CLI's hook system cannot prompt users (only allow/block). Use the policy engine instead:

bash-gates --export-toml > ~/.gemini/policies/bash-gates.toml

This exports 700+ policy rules derived from the gate definitions:

Priority Action Examples
900+ deny rm -rf /, gh repo delete
200-299 ask_user npm install, git push
100-199 allow git status, ls, cat
1 ask_user Default fallback for unknown commands

Links

About

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

Topics

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages