Yesterday I published a safety guide for Claude Code Auto Mode. Several people asked for the actual implementation - not just the concepts, but working code they could drop into a project today.
Here it is. A kill switch that monitors Claude Code sessions for destructive operations and stops them before they execute.
The Problem
Claude Code Auto Mode executes commands without asking. Most of those commands are fine - creating files, running tests, editing code. But some commands can destroy production infrastructure in seconds: terraform destroy, rm -rf, DROP TABLE, aws rds delete-db-instance.
You need a layer between Claude Code and your shell that catches these before they run.
The Implementation
This is a bash wrapper that intercepts commands and checks them against a blocklist before execution:
#!/bin/bash
# claude-guard.sh - Kill switch for Claude Code Auto Mode
# Drop this in your project root and use it as your shell wrapper
set -euo pipefail
KILL_FILE="${CLAUDE_GUARD_KILL_FILE:-.claude-stop}"
LOG_FILE="${CLAUDE_GUARD_LOG:-/tmp/claude-guard.log}"
BLOCKED_LOG="${CLAUDE_GUARD_BLOCKED:-/tmp/claude-blocked.log}"
# Destructive patterns - add your own
DESTRUCTIVE_PATTERNS=(
'terraform destroy'
'terraform apply -destroy'
'rm -rf /'
'rm -rf /*'
'DROP TABLE'
'DROP DATABASE'
'DELETE FROM .* WHERE 1'
'aws rds delete-db'
'aws s3 rb'
'aws ec2 terminate'
'kubectl delete namespace'
'kubectl delete -f .* --all'
'docker system prune -a'
'git push.*--force.*main'
'git push.*--force.*master'
'truncate table'
)
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
}
block() {
local cmd="$1"
local pattern="$2"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] BLOCKED: '$cmd' matched pattern: '$pattern'" >> "$BLOCKED_LOG"
echo "BLOCKED: Destructive operation detected."
echo "Pattern matched: $pattern"
echo "Command: $cmd"
echo "To override, remove the matching pattern from claude-guard.sh"
exit 1
}
# Check kill switch
if [ -f "$KILL_FILE" ]; then
echo "KILL SWITCH ACTIVE - All Claude Code operations halted"
echo "Remove $KILL_FILE to resume"
exit 1
fi
# Get the command to execute
CMD="$*"
if [ -z "$CMD" ]; then
echo "Usage: claude-guard.sh <command>"
exit 1
fi
# Check against destructive patterns
for pattern in "${DESTRUCTIVE_PATTERNS[@]}"; do
if echo "$CMD" | grep -iqE "$pattern"; then
block "$CMD" "$pattern"
fi
fi
# Command passed - log and execute
log "ALLOWED: $CMD"
exec $CMD
How to Use It
1. Save the script:
curl -o claude-guard.sh https://gist.githubusercontent.com/.../claude-guard.sh
chmod +x claude-guard.sh
2. Use it as a command prefix:
./claude-guard.sh terraform apply
# Works fine - 'apply' isn't blocked
./claude-guard.sh terraform destroy
# BLOCKED: Destructive operation detected.
# Pattern matched: terraform destroy
3. Emergency stop - create the kill file:
touch .claude-stop
# Now ALL commands through claude-guard.sh are blocked
rm .claude-stop
# Resume normal operations
Python Version With Slack Alerts
If you want notifications when something gets blocked:
#!/usr/bin/env python3
"""claude_guard.py - Kill switch with Slack alerting."""
import sys
import os
import re
import json
import subprocess
from datetime import datetime
from pathlib import Path
try:
from urllib.request import Request, urlopen
except ImportError:
urlopen = None
KILL_FILE = os.environ.get('CLAUDE_GUARD_KILL_FILE', '.claude-stop')
SLACK_WEBHOOK = os.environ.get('CLAUDE_GUARD_SLACK_WEBHOOK', '')
DESTRUCTIVE_PATTERNS = [
r'terraform\s+destroy',
r'terraform\s+apply\s+-destroy',
r'rm\s+-rf\s+/',
r'DROP\s+(TABLE|DATABASE)',
r'DELETE\s+FROM\s+.*WHERE\s+1',
r'aws\s+rds\s+delete-db',
r'aws\s+s3\s+rb',
r'aws\s+ec2\s+terminate',
r'kubectl\s+delete\s+(namespace|ns)',
r'git\s+push.*--force.*(main|master)',
]
def alert_slack(cmd: str, pattern: str):
if not SLACK_WEBHOOK or not urlopen:
return
payload = json.dumps({
'text': f':rotating_light: Claude Guard BLOCKED a destructive command:\n'
f'```
{% endraw %}
{cmd}
{% raw %}
```\nPattern: `{pattern}`\n'
f'Time: {datetime.now().isoformat()}\n'
f'Host: {os.uname().nodename}'
}).encode()
req = Request(SLACK_WEBHOOK, data=payload,
headers={'Content-Type': 'application/json'})
try:
urlopen(req, timeout=5)
except Exception:
pass # Don't let alerting failure block the guard
def main():
if Path(KILL_FILE).exists():
print('KILL SWITCH ACTIVE - All operations halted')
sys.exit(1)
if len(sys.argv) < 2:
print('Usage: claude_guard.py <command>')
sys.exit(1)
cmd = ' '.join(sys.argv[1:])
for pattern in DESTRUCTIVE_PATTERNS:
if re.search(pattern, cmd, re.IGNORECASE):
print(f'BLOCKED: Destructive operation detected.')
print(f'Pattern: {pattern}')
print(f'Command: {cmd}')
alert_slack(cmd, pattern)
sys.exit(1)
# Safe to execute
result = subprocess.run(sys.argv[1:], capture_output=False)
sys.exit(result.returncode)
if __name__ == '__main__':
main()
Set CLAUDE_GUARD_SLACK_WEBHOOK to your Slack incoming webhook URL and you'll get an alert any time a destructive command is caught.
Customizing the Patterns
The default patterns cover the obvious cases. You'll want to add patterns specific to your stack:
-
Using Prisma? Add
prisma migrate reset -
Using Docker Compose in production? Add
docker-compose down -v -
Running Kubernetes? Add
helm uninstallfor critical releases -
Using Fly.io? Add
fly apps destroy
The patterns are regular expressions. Be as specific as you need - rm -rf /var/data is more targeted than rm -rf (which would also block rm -rf node_modules, which is usually fine).
Limitations
This catches direct command invocations. It won't catch:
- Claude Code writing a Python script that calls
os.system('terraform destroy')and then running that script - Destructive operations embedded in Makefiles or shell scripts that Claude Code invokes
- API calls made directly through curl or SDK code
For those cases, the AWS permission boundary approach from the safety guide is your backstop. The guard catches the easy cases; IAM boundaries catch everything else.
No single layer is enough. Use both.
This article was written with AI assistance. All technical claims, code, and architectural decisions were validated by the author.
Top comments (0)