#!/usr/bin/env python # -*- coding: utf-8 -*- """ PreToolUse hook — 조직 공용 안전 도구 자동 승인 Live 체계의 권한 레이어 확장 (C28 문서 수정 무승인 원칙의 즉시 적용) stdin JSON: {tool_name, tool_input} stdout JSON: {hookSpecificOutput: {permissionDecision: allow|deny|ask}} """ import json import sys import io import re # Windows CP949 환경에서 UTF-8 출력 강제 sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8") def respond(decision, reason): out = { "hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": decision, "permissionDecisionReason": reason, } } sys.stdout.write(json.dumps(out, ensure_ascii=False)) sys.stdout.flush() sys.exit(0) def main(): raw = sys.stdin.read() try: data = json.loads(raw) except Exception: respond("ask", "input parse failed") tool_name = data.get("tool_name", "") tool_input = data.get("tool_input", {}) or {} # 시스템 경로 차단 (Edit·Write·MultiEdit) if tool_name in ("Edit", "Write", "MultiEdit"): fp = (tool_input.get("file_path") or "").replace("\\", "/").lower() if fp.startswith("/etc/") or fp.startswith("/system/") or fp.startswith("/windows/"): respond("deny", "system directory blocked") if re.match(r"^[a-z]:/windows/", fp): respond("deny", "Windows system directory blocked") # 도구별 판정 if tool_name in ("Read", "Glob", "Grep", "TodoWrite", "WebFetch", "WebSearch", "NotebookEdit"): respond("allow", "safe read/search/todo") if tool_name in ("Edit", "Write", "MultiEdit"): respond("allow", "doc/code edit (C28)") if tool_name.startswith("mcp__"): respond("allow", "MCP tool") if tool_name in ("Agent", "Task", "Skill"): respond("allow", "subagent/skill") if tool_name == "Bash": cmd = (tool_input.get("command") or "").strip() danger = [ r"^rm(\s|$)", r"^rmdir(\s|$)", r"^sudo(\s|$)", r"^dd\s", r"^mkfs", r"^format\s", r"^shutdown", r"^reboot", r"^chmod\s+777", r"^chown\s", ] for pat in danger: if re.search(pat, cmd): respond("deny", "dangerous bash: " + pat) respond("allow", "safe bash") respond("ask", "unknown tool " + tool_name) if __name__ == "__main__": main()