Persist destructive/infra footgun hooks in the repo and teach install.sh to sync them without clobbering a local hooks.json unless overwritten.
60 lines
1.6 KiB
Bash
Executable File
60 lines
1.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# Cursor hook: beforeShellExecution
|
|
# Purpose: deny obviously destructive commands by default, cursor-wide.
|
|
# Note: read stdin before the Python heredoc (heredoc would otherwise consume it).
|
|
|
|
INPUT="$(cat)"
|
|
|
|
python3 -c '
|
|
import json, re, sys
|
|
|
|
raw = sys.argv[1]
|
|
try:
|
|
data = json.loads(raw) if raw.strip() else {}
|
|
except Exception:
|
|
print(json.dumps({
|
|
"permission": "deny",
|
|
"user_message": "Blocked: safety hook could not parse shell request input.",
|
|
"agent_message": "Hook failed to parse input JSON; failing closed."
|
|
}))
|
|
raise SystemExit(0)
|
|
|
|
cmd = (data.get("command") or "").strip()
|
|
cmd_l = cmd.lower()
|
|
|
|
blocked_patterns = [
|
|
r"\brm\b.*\s-(?:rf|fr)\b",
|
|
r"\brm\b.*\s--no-preserve-root\b",
|
|
r"\bmkfs(\.\w+)?\b",
|
|
r"\bdd\b\s+if=",
|
|
r"\bwipefs\b",
|
|
r"\bshred\b",
|
|
r"\btruncate\b.*\s-s\s*0\b",
|
|
r"\bgit\s+push\b.*\s--force(?:-with-lease)?\b",
|
|
r"\bgit\s+reset\b.*\s--hard\b",
|
|
r"\bgit\s+clean\b.*\s-(?:ff|fd|xdf)\b",
|
|
r"\bterraform\s+destroy\b",
|
|
r"\bterraform\s+apply\b(?!.*\s-auto-approve\b)",
|
|
r"\bkubectl\s+delete\b",
|
|
r"\bansible-playbook\b.*\s--flush-cache\b",
|
|
]
|
|
|
|
blocked_literals = {
|
|
"sudo rm -rf /",
|
|
"rm -rf /",
|
|
}
|
|
|
|
is_blocked = cmd_l in blocked_literals or any(re.search(p, cmd_l) for p in blocked_patterns)
|
|
|
|
if is_blocked:
|
|
print(json.dumps({
|
|
"permission": "deny",
|
|
"user_message": "Blocked: potentially destructive command (Cursor safety hook).",
|
|
"agent_message": f"Denied by global hook. Command was: {cmd}"
|
|
}))
|
|
else:
|
|
print(json.dumps({"permission": "allow"}))
|
|
' "$INPUT"
|