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

271 lines
8.3 KiB
Markdown

# 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`
```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`:
```json
{
"PreToolUse": [
{
"matcher": "Bash|Write|Edit",
"hooks": [
{
"type": "command",
"command": "~/.claude/hooks/scripts/guardrail.py",
"timeout": 5
}
]
}
]
}
```
### Hook Input (stdin)
```json
{
"tool_name": "Bash",
"tool_input": {
"command": "rm -rf ~/Downloads/old-project"
}
}
```
### Hook Output (stdout)
Allow:
```json
{"decision": "allow"}
```
Block:
```json
{
"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:**
```bash
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`
```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
```json
{"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 |