76 lines
2.4 KiB
Python
76 lines
2.4 KiB
Python
#!/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()
|