Files
claude-code/docs/plans/2025-01-06-guardrail-hooks-design.md
OpenCode Test f2f8a03a32 Add guardrail hooks design document
PreToolUse hooks to prevent dangerous operations:
- Intercepts Bash, Write, Edit before execution
- Contextual response (block vs confirm)
- Path-aware with git repo detection
- Session allowlist for user confirmations
- Audit logging

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 10:50:03 -08:00

8.3 KiB

Guardrail Hooks Design

Date: 2025-01-06 Status: Approved

Overview

PreToolUse guardrail hooks that prevent dangerous operations by intercepting Bash, Write, and Edit tool calls before execution.

Goals

  • Prevent catastrophic operations (destructive file ops, dangerous system commands, infrastructure mistakes)
  • Contextual response: hard block for severe threats, confirmation for moderate risks
  • Path-aware: operations inside projects are more permissive than outside
  • Auditable: log all interventions for review

Non-Goals

  • Git command guardrails (future consideration)
  • Rate limiting or resource protection
  • Workflow compliance enforcement

Architecture

~/.claude/
├── hooks/
│   └── hooks.json                    # Hook registration (add PreToolUse)
├── hooks/scripts/
│   ├── guardrail.py                  # Main PreToolUse logic
│   └── guardrail-confirm.py          # Adds operation to session allowlist
├── state/
│   ├── guardrails.json               # Rules configuration
│   └── guardrail-session.json        # Session allowlist (cleared on session end)
└── logs/
    └── guardrail.jsonl               # Audit log (blocked/confirmed only)

Flow

  1. Claude invokes Bash/Write/Edit
  2. guardrail.py runs via PreToolUse hook
  3. Check session allowlist - if present, allow
  4. Evaluate rules + path context - decide action
  5. Allow: return {"decision": "allow"}
  6. Block/Confirm: log, return {"decision": "block", "reason": "..."}
  7. For confirm: user approves, Claude calls guardrail-confirm.py, retries

Configuration

Location: ~/.claude/state/guardrails.json

{
  "version": 1,
  "safe_paths": [
    "~/.claude",
    "~/projects"
  ],
  "blocked_paths": [
    "/etc", "/usr", "/var", "/boot", "/sys", "/proc",
    "~/.ssh", "~/.gnupg", "~/.aws"
  ],
  "rules": {
    "bash": [
      {"pattern": "rm -rf /", "action": "block"},
      {"pattern": "rm -rf ~", "action": "block"},
      {"pattern": "rm -rf \\*", "action": "block"},
      {"pattern": "chmod -R 777", "action": "block"},
      {"pattern": ":(){ :|:& };:", "action": "block"},
      {"pattern": "mkfs\\.", "action": "block"},
      {"pattern": "dd if=.* of=/dev/", "action": "block"},
      {"pattern": "> /dev/sda", "action": "block"},
      {"pattern": "shutdown", "action": "confirm"},
      {"pattern": "reboot", "action": "confirm"},
      {"pattern": "systemctl (stop|disable|mask)", "action": "confirm"},
      {"pattern": "rm ", "action": "confirm", "outside_safe_paths": true},
      {"pattern": "kubectl delete", "action": "confirm"},
      {"pattern": "docker rm", "action": "confirm"}
    ],
    "write": [
      {"path_patterns": ["blocked_paths"], "action": "block"},
      {"path_patterns": ["outside_safe_paths"], "action": "confirm"}
    ],
    "edit": [
      {"path_patterns": ["blocked_paths"], "action": "block"},
      {"path_patterns": ["outside_safe_paths"], "action": "confirm"}
    ]
  }
}

Key Concepts

  • pattern: regex matched against command/path
  • action: block (hard stop) or confirm (require approval)
  • outside_safe_paths: rule only applies when target is outside safe directories
  • Bash rules check command string; Write/Edit rules check file path

Safe Paths Logic

Evaluation order (first match wins):

  1. Blocked paths check - Is target in blocked_paths?

    • Yes: apply block/confirm regardless of other factors
    • Protects system-critical locations absolutely
  2. Explicit allowlist - Is target under a safe_paths entry?

    • Yes: path is safe
    • Supports glob patterns (~/projects/*)
  3. Git-aware detection - Is target inside a git repository?

    • Walk up from target path looking for .git directory
    • Found: treat as safe (it's an intentional project)
  4. Default - Path is outside safe areas

    • Operations here trigger outside_safe_paths rules

Path Normalization

  • Expand ~ to actual home directory
  • Resolve symlinks to real paths
  • Handle relative paths by resolving against CWD

Examples

Target Path Result Reason
/etc/hosts blocked/confirm In blocked_paths
~/.ssh/config blocked/confirm In blocked_paths
~/.claude/hooks/test.py safe In safe_paths
~/projects/myapp/src/main.py safe In safe_paths
~/random-repo/file.txt safe Git repo detected
~/Downloads/file.txt outside No match, triggers rules

Edge case: If a git repo exists under a blocked path (unlikely), blocked path wins.

Hook Implementation

Registration

Add to ~/.claude/hooks/hooks.json:

{
  "PreToolUse": [
    {
      "matcher": "Bash|Write|Edit",
      "hooks": [
        {
          "type": "command",
          "command": "~/.claude/hooks/scripts/guardrail.py",
          "timeout": 5
        }
      ]
    }
  ]
}

Hook Input (stdin)

{
  "tool_name": "Bash",
  "tool_input": {
    "command": "rm -rf ~/Downloads/old-project"
  }
}

Hook Output (stdout)

Allow:

{"decision": "allow"}

Block:

{
  "decision": "block",
  "reason": "GUARDRAIL BLOCKED: 'rm' outside safe paths.\nTarget: ~/Downloads/old-project\nRule: rm_outside_safe\n\nTo proceed, confirm with user then run:\npython ~/.claude/hooks/scripts/guardrail-confirm.py 'rm -rf ~/Downloads/old-project'"
}

Confirmation Flow

  1. Guardrail blocks with detailed message including confirm command
  2. Claude reports to user, asks for confirmation
  3. User confirms in conversation
  4. Claude runs confirm script:
    python ~/.claude/hooks/scripts/guardrail-confirm.py "rm -rf ~/Downloads/old-project"
    
  5. Confirm script adds to session allowlist
  6. Claude retries the original command
  7. Guardrail checks allowlist - finds match - allows

Session Allowlist

Location: ~/.claude/state/guardrail-session.json

{
  "confirmed": [
    {"tool": "Bash", "operation": "rm -rf ~/Downloads/old-project", "ts": "2025-01-06T10:24:00"}
  ]
}

Cleanup: session-end.sh clears this file so confirmations don't persist across sessions.

Matching: Exact match on tool + operation string.

Logging

Location: ~/.claude/logs/guardrail.jsonl

Format: JSON Lines (append-only)

Logged events:

  • Every block action
  • Every confirm action (required approval)
  • When confirmed operation is allowed

Log Entry Structure

{"ts": "2025-01-06T10:23:45", "tool": "Bash", "operation": "rm -rf /", "action": "block", "rule": "rm_rf_root", "path_context": "n/a"}
{"ts": "2025-01-06T10:24:01", "tool": "Bash", "operation": "rm ~/Downloads/old", "action": "confirm_required", "rule": "rm_outside_safe", "path_context": "outside"}
{"ts": "2025-01-06T10:24:30", "tool": "Bash", "operation": "rm ~/Downloads/old", "action": "confirmed_allow", "rule": "session_allowlist"}

Fields

  • ts: ISO 8601 timestamp
  • tool: Bash, Write, or Edit
  • operation: Command or file path
  • action: block, confirm_required, or confirmed_allow
  • rule: Which rule triggered
  • path_context: safe, outside, blocked, or n/a

Implementation Checklist

  • Create state/guardrails.json with starter rules
  • Create hooks/scripts/guardrail.py main logic
  • Create hooks/scripts/guardrail-confirm.py confirm helper
  • Modify hooks/hooks.json to add PreToolUse registration
  • Modify hooks/scripts/session-end.sh to clear session allowlist
  • Create logs/ directory
  • Test: block scenario (catastrophic command)
  • Test: confirm scenario (rm outside safe paths)
  • Test: allow scenario (operation in safe path)
  • Test: git-aware detection

Starter Rules

Category Pattern Action
Catastrophic rm -rf /, rm -rf ~, mkfs.*, dd.*of=/dev/ block
Fork bomb :(){ :|:& };: block
Dangerous chmod chmod -R 777 block
System commands shutdown, reboot confirm
Service control systemctl (stop|disable|mask) confirm
Destructive outside safe rm (outside safe paths) confirm
K8s destructive kubectl delete confirm
Docker destructive docker rm, docker system prune confirm
Write to blocked paths Any Write/Edit to /etc, ~/.ssh, etc. block
Write outside safe Any Write/Edit outside safe paths confirm