-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathblock_rm_rf.py
More file actions
executable file
·62 lines (48 loc) · 1.76 KB
/
Copy pathblock_rm_rf.py
File metadata and controls
executable file
·62 lines (48 loc) · 1.76 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
#!/usr/bin/env python3
"""PreToolUse hook: block `rm -rf` (recursive + force delete).
Claude Code pipes the Bash tool call to this script as JSON on stdin. If the
command is a recursive-force `rm`, we print a "deny" decision and Claude Code
refuses to run it. Printing nothing at all means "allow".
"""
import json
import re
import sys
try:
from hook_logger import log, log_payload
except Exception: # logging must never break the safety hook
def log(*args, **kwargs):
pass
def log_payload(*args, **kwargs):
pass
# Match `rm` that has BOTH a recursive flag (-r / -R / -rf / --recursive) and a
# force flag (-f / -rf / --force), in any order.
RM_RF = re.compile(
r"\brm\b"
r"(?=.*(?:-\w*[rR]|--recursive))" # ...followed by a recursive flag
r"(?=.*(?:-\w*f|--force))", # ...followed by a force flag
)
def is_dangerous_rm(command: str) -> bool:
return bool(RM_RF.search(command))
def main() -> int:
try:
payload = json.load(sys.stdin)
except (json.JSONDecodeError, ValueError):
return 0 # nothing to inspect -> allow
log_payload("block_rm_rf", payload)
command = (payload.get("tool_input") or {}).get("command", "")
blocked = bool(command) and is_dangerous_rm(command)
log("block_rm_rf", command=command, decision="deny" if blocked else "allow")
if blocked:
print(json.dumps({
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": (
"Blocked by project hook: rm -rf (recursive force delete) "
"is not permitted."
),
}
}))
return 0
if __name__ == "__main__":
sys.exit(main())