Skip to content

Latest commit

 

History

History
124 lines (96 loc) · 3.73 KB

File metadata and controls

124 lines (96 loc) · 3.73 KB

Claude Bash Hook

PreToolUse hook for Claude Code that provides granular permission control over Bash and Nushell commands.

Architecture

  • main.rs - Hook entry point, reads JSON from stdin, outputs permission decisions
  • analyzer.rs - Bash AST parsing using tree-sitter-bash
  • nushell.rs - Nushell command parsing using nu-parser
  • config.rs - TOML config loading and rule matching (glob patterns, subcommand detection)
  • wrappers/ - Unwrap wrapper commands to analyze inner commands:
    • Config-driven: sudo, authsudo, nice, nohup, time, strace, ltrace, nu, fish
    • Special handling: ssh, scp, rsync, env, kubectl exec, docker exec/compose, timeout, xargs, sh/bash/zsh, kitty-remote, wezterm-remote
  • scripts/ - Script content analysis (parses inline code to allow safe read-only scripts):
    • python.rs - Python via -c or heredoc (allows file I/O, denies subprocess/os.system/eval)
    • shell.rs - Shell scripts via sh -c / bash -c (re-parses inner commands through the same rule engine)
    • php.rs - PHP via -r flag (allows read-only operations, denies exec/system/passthru)
  • sql.rs - MySQL/MariaDB/SQLite query analysis (allow SELECT, ask for writes)
  • redis.rs - Redis command analysis (allow read-only commands like GET/LLEN, ask for writes)
  • git.rs - Git-specific rules (push branch protection, checkout handling)
  • docker.rs - Docker run bind mount analysis
  • tar.rs - Tar extraction path validation
  • rm.rs - Delete command path validation
  • tee.rs - Tee output path validation
  • advice.rs - Optional AI-powered advice for permission decisions

Build

cargo build --release

Binary: target/release/claude-bash-hook

Symlinked to: /home/osso/bin/claude-bash-hook

Config

Location: ~/.config/claude-bash-hook/config.toml

Default config embedded in binary (config.default.toml). Copy and customize:

mkdir -p ~/.config/claude-bash-hook
cp config.default.toml ~/.config/claude-bash-hook/config.toml

Config Format

default = "passthrough"  # allow, ask, deny, passthrough
enable_advice = false    # AI-powered advice for ask/deny decisions

[[rules]]
commands = ["ls", "cat", "git status"]  # Command or command+subcommand patterns
permission = "allow"                     # allow, ask, deny, passthrough, check_host
reason = "read-only commands"
cwd = "/home/user/project"              # Optional: only match in this directory tree

[[rules]]
commands = ["ssh", "scp"]
permission = "check_host"
reason = "remote connection"
host_rules = [
    { pattern = "*.internal.com", permission = "allow" },
    { pattern = "*", permission = "ask" },
]

[[wrappers]]
command = "sudo"
opts_with_args = ["-u", "-g"]  # Options that consume the next argument

[[suggestions]]
command = "git checkout"
message = "Consider using 'git switch' instead"

Permission Levels

  • allow - Auto-approve, no user prompt
  • passthrough - Let Claude Code's built-in system handle (for Bash only; becomes "ask" for Nushell MCP)
  • ask - Prompt user for approval
  • deny - Block with reason

Hook Protocol

Receives JSON on stdin:

{
  "tool_name": "Bash",
  "tool_input": { "command": "ls -la" },
  "permission_mode": "default",
  "cwd": "/home/user/project"
}

Outputs JSON to stdout (or nothing for passthrough):

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow",
    "permissionDecisionReason": "read-only commands"
  }
}

Also handles:

  • Write tool - blocks /tmp/* unless under /tmp/claude/*
  • mcp__nushell__execute - Nushell MCP tool (passthrough becomes ask)

Testing

cargo test

Logging

Logs to journald with identifier claude-bash-hook:

journalctl -t claude-bash-hook -f