forked from FlorianBruniaux/claude-code-ultimate-guide
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdangerous-actions-blocker.sh
More file actions
executable file
·151 lines (128 loc) · 4.27 KB
/
Copy pathdangerous-actions-blocker.sh
File metadata and controls
executable file
·151 lines (128 loc) · 4.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
#!/bin/bash
# Hook: PreToolUse - Block dangerous actions
# Exit 0 = allow, Exit 2 = block (stderr message shown to Claude)
#
# Place in: .claude/hooks/dangerous-actions-blocker.sh
# Register in: .claude/settings.json under PreToolUse event
set -e
# Read JSON from stdin
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty')
TOOL_INPUT=$(echo "$INPUT" | jq -r '.tool_input // empty')
# === BASH: Dangerous commands ===
if [[ "$TOOL_NAME" == "Bash" ]]; then
COMMAND=$(echo "$TOOL_INPUT" | jq -r '.command // empty')
# Dangerous patterns
DANGEROUS_PATTERNS=(
"rm -rf /"
"rm -rf ~"
"rm -rf \$HOME"
"dd if="
"mkfs"
":(){:|:&};:" # Fork bomb
"> /dev/sda"
"chmod -R 777 /"
"chown -R"
"sudo rm"
"DROP DATABASE"
"DROP TABLE"
"--no-preserve-root"
)
for pattern in "${DANGEROUS_PATTERNS[@]}"; do
if [[ "$COMMAND" == *"$pattern"* ]]; then
echo "BLOCKED: Dangerous command detected: '$pattern'" >&2
exit 2
fi
done
# Block force push to main/master
if echo "$COMMAND" | grep -qE "git push.*(-f|--force).*(main|master)"; then
echo "BLOCKED: Force push to main/master is forbidden" >&2
exit 2
fi
# Block npm publish without confirmation
if echo "$COMMAND" | grep -qE "npm publish|pnpm publish|yarn publish"; then
echo "BLOCKED: Package publication requires manual confirmation" >&2
exit 2
fi
# Check for potential secrets in command
SECRET_PATTERNS=(
"password="
"secret="
"api_key="
"apikey="
"token="
"aws_access_key"
"aws_secret"
"private_key"
)
for pattern in "${SECRET_PATTERNS[@]}"; do
if echo "$COMMAND" | grep -qi "$pattern"; then
echo "BLOCKED: Potential secret detected in command: '$pattern'" >&2
exit 2
fi
done
fi
# === EDIT/WRITE: Sensitive files ===
if [[ "$TOOL_NAME" == "Edit" || "$TOOL_NAME" == "Write" ]]; then
FILE_PATH=$(echo "$TOOL_INPUT" | jq -r '.file_path // empty')
# Protected files
PROTECTED_FILES=(
".env"
".env.local"
".env.production"
".env.development"
"credentials.json"
"serviceAccountKey.json"
"id_rsa"
"id_ed25519"
"id_ecdsa"
".npmrc"
".pypirc"
"secrets.yml"
"secrets.yaml"
)
FILENAME=$(basename "$FILE_PATH")
for protected in "${PROTECTED_FILES[@]}"; do
if [[ "$FILENAME" == "$protected" ]]; then
echo "BLOCKED: Editing sensitive file '$FILENAME' is forbidden" >&2
exit 2
fi
done
# Block editing outside project (with configurable exceptions)
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
CLAUDE_HOME="${HOME}/.claude"
# Allowed paths (configurable via environment variable)
# Format: colon-separated paths - e.g., ALLOWED_PATHS="/custom/path:/other/path"
EXTRA_ALLOWED="${ALLOWED_PATHS:-}"
# Check if path is allowed
is_allowed=false
# Current project
[[ "$FILE_PATH" == "$PROJECT_DIR"* ]] && is_allowed=true
# Claude Code directory (~/.claude/) - plans, logs, settings
[[ "$FILE_PATH" == "$CLAUDE_HOME"* ]] && is_allowed=true
# Temporary files
[[ "$FILE_PATH" == "/tmp"* ]] && is_allowed=true
# Additional configured paths
if [[ -n "$EXTRA_ALLOWED" ]]; then
IFS=':' read -ra EXTRA_PATHS <<< "$EXTRA_ALLOWED"
for allowed_path in "${EXTRA_PATHS[@]}"; do
[[ "$FILE_PATH" == "$allowed_path"* ]] && is_allowed=true
done
fi
if [[ "$is_allowed" == "false" ]]; then
echo "BLOCKED: Editing outside project is forbidden: $FILE_PATH" >&2
echo "Allowed paths: $PROJECT_DIR, $CLAUDE_HOME, /tmp" >&2
[[ -n "$EXTRA_ALLOWED" ]] && echo "Additional allowed: $EXTRA_ALLOWED" >&2
exit 2
fi
fi
# === DELETE: Always warn ===
if [[ "$TOOL_NAME" == "Bash" ]]; then
COMMAND=$(echo "$TOOL_INPUT" | jq -r '.command // empty')
if echo "$COMMAND" | grep -qE "rm -r|rmdir|unlink"; then
# Warning but not blocking (exit 0)
echo '{"systemMessage": "Warning: File deletion detected. Verify this is intentional."}'
fi
fi
# Allow by default
exit 0