#!/usr/bin/env python3
"""
.claude/hooks/guard-destructive.py -- PreToolUse belt-and-suspenders for the
jumphost (2026-07-03). Settings deny/ask rules are the first line; this hook
exists because (a) a hook exit-2 blocks BEFORE permission evaluation in every
permission mode, and (b) Bash settings-rule enforcement has a documented
reliability history upstream. Blocks the NEVER class and secret-file shell
reads that Read() rules cannot see (arbitrary subprocess reads).
stdin: PreToolUse JSON. exit 0 = no opinion (permission rules proceed);
exit 2 = hard block (stderr shown to Claude). ASCII + LF.
Offline test: tests/claude-guard/run-tests.sh.
"""
import json
import re
import sys
NEVER = [
(r"vault\s+operator\s+(init|rekey|generate-root)",
"one-shot vault operation: operator-only, from the runbook, VERBATIM (DOCFIX-006/D-069)"),
(r"juju\s+destroy-controller",
"controller destruction is out of scope for any session on this host"),
(r"\bmaas\s+list\b",
"prints the MAAS API key (DOCFIX-016); use 'maas admin ...' directly"),
(r"git\s+push\s+(--force|-f)\b",
"force-push is banned on this repo"),
(r"(cat|less|more|head|tail|cp|scp|base64|xxd|od|strings)\b[^|;&]*"
r"(vault-init/|as-executed/|-cred\.txt|appcred)",
"secret-adjacent file: never read key/cred material into context (whitelist-print rule)"),
(r"rm\s+-rf\s+(/|~)\s*$",
"catastrophic rm"),
]
def main():
try:
data = json.load(sys.stdin)
except Exception:
return 0 # malformed input: no opinion; permission rules still apply
cmd = (data.get("tool_input") or {}).get("command", "") or ""
for rx, why in NEVER:
if re.search(rx, cmd):
sys.stderr.write(
"BLOCKED by .claude/hooks/guard-destructive.py: %s\n" % why)
return 2
return 0
if __name__ == "__main__":
sys.exit(main())