#!/usr/bin/env python3 """PreToolUse guard for the Bash tool (EDEN, Master Prompt §2.1/§3.4). Reads the hook input JSON on stdin, inspects tool_input.command, and emits a PreToolUse decision (allow/ask/deny) as JSON on stdout. Fails open (allow) on any internal error so this hook can never itself break a legitimate command — it only ever tightens, never crashes the turn. """ import json import re import sys PROJECT_ROOT = "/opt/eden" DENY_PATTERNS = [ (r"\brm\s+-rf\s+/(\s|$)", "rm -rf / — apagaria o filesystem inteiro"), (r"\brm\s+-rf\s+/\*", "rm -rf /* — apagaria o filesystem inteiro"), (r"\bdocker\s+system\s+prune\b", "docker system prune — remoção ampla, pode afetar outros projetos no host"), (r"\bdocker\s+volume\s+prune\b", "docker volume prune — remoção ampla de volumes, pode afetar outros projetos"), ] ASK_PATTERNS = [ (r"\bdocker\s+volume\s+rm\b", "remoção de volume Docker — confirme que o volume pertence ao EDEN"), (r"(^|\s)ssh\s+\S+@", "SSH para host externo"), (r"(^|\s)scp\s+.*:", "SCP para/de host externo"), (r"(^|\s)rsync\s+.*\S+@\S+:", "rsync para/de host externo"), (r"\b(cat|less|more|head|tail)\s+[^\n|;&]*\.env(\.[a-zA-Z0-9_]+)?\b", "leitura/impressão de arquivo .env"), (r"\b(cat|less|more|head|tail)\s+[^\n|;&]*(credential|secret)", "leitura/impressão de arquivo de credencial/segredo"), (r"~/\.ssh", "acesso a ~/.ssh"), (r"(^|\s)/etc(/|\s|$)", "acesso a /etc"), (r"(^|\s)/root(/|\s|$)", "acesso a /root"), ] def outside_project_paths(command: str): """Find absolute paths referenced in the command that live outside /opt/eden but under /opt/ (i.e. plausibly another project on this host).""" hits = [] for m in re.finditer(r"/opt/([a-zA-Z0-9_.\-]+)(/\S*)?", command): full = m.group(0) if not full.startswith(PROJECT_ROOT): hits.append(full) return hits def decide(command: str): for pattern, reason in DENY_PATTERNS: if re.search(pattern, command, re.IGNORECASE): return "deny", reason other_projects = outside_project_paths(command) if other_projects: return "deny", f"referencia caminho fora de {PROJECT_ROOT}: {', '.join(other_projects[:3])}" for pattern, reason in ASK_PATTERNS: if re.search(pattern, command, re.IGNORECASE): return "ask", reason return "allow", None def main(): try: payload = json.load(sys.stdin) command = (payload.get("tool_input") or {}).get("command", "") or "" except Exception: # Fail open: if we can't parse input, don't block the tool call. print(json.dumps({})) return decision, reason = decide(command) if decision == "allow": print(json.dumps({})) return output = { "hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": decision, "permissionDecisionReason": f"[eden-guard] {reason} (EDEN_MASTER_PROMPT_CLAUDE.md §2.1/§3.4)", } } print(json.dumps(output)) if __name__ == "__main__": main()