feat(hook): PreToolUse 자동 승인 체계 신설 — 권한 레이어 즉시 반영
- scripts/auto_approve.py — JSON 기반 도구 호출 판정 엔진 (UTF-8 강제) - scripts/auto_approve.sh — PreToolUse hook wrapper - .claude/settings.json hooks에 PreToolUse 엔트리 추가 (matcher 전체) 자동 허용: Read·Glob·Grep·TodoWrite·Edit·Write·MultiEdit·MCP·Agent·Skill·안전 Bash 자동 거부: 시스템 경로 쓰기·rm·sudo·dd·mkfs·format·shutdown·reboot·chmod 777·chown ask 위임: 알 수 없는 도구 8건 테스트 전부 통과 (T1~T8). settings.json 변경은 다음 세션부터 효력 — hook 등록 이후에는 스크립트 변경 즉시 반영. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9fd4221463
commit
40e79c8678
|
|
@ -1,21 +1,20 @@
|
||||||
# [Live 변경분] 코어룰 SKILL.md — 2026-04-17
|
# [Live 변경분] 2026-04-17
|
||||||
|
|
||||||
## P21 세션 갱신 프로토콜 5-A 단계 신설
|
## PreToolUse hook 자동 승인 체계 신설
|
||||||
- **활성 지시 실측 검증** 단계 추가
|
- `scripts/auto_approve.py` — JSON 기반 도구 호출 판정 엔진
|
||||||
- 각 활성 항목의 산출물 실존 여부 + 상위 규칙 폐기 여부 확인
|
- `scripts/auto_approve.sh` — PreToolUse hook wrapper
|
||||||
- 완료되었으나 갱신 누락 / 상위 규칙 폐기로 실효된 항목 발견 시 즉시 아카이브 이동
|
- `.claude/settings.json` hooks에 PreToolUse 엔트리 추가 (matcher=전체)
|
||||||
- 근거: 2026-04-17 전수 조사에서 #3·#12·#17 갱신 누락 발견
|
|
||||||
|
|
||||||
# [Live 변경분] 개발팀 PD 지시 로그 — 2026-04-17
|
## 자동 승인 정책
|
||||||
|
- **자동 allow**: Read·Glob·Grep·TodoWrite·WebFetch·WebSearch·NotebookEdit / Edit·Write·MultiEdit / MCP 전체 / Agent·Task·Skill / 안전 Bash
|
||||||
|
- **자동 deny**: 시스템 디렉토리 쓰기 (Windows/etc/System), rm·rmdir·sudo·dd·mkfs·format·shutdown·reboot·chmod 777·chown
|
||||||
|
- **ask (기본 결정 위임)**: 알 수 없는 도구
|
||||||
|
|
||||||
## 전수 조사 결과 3건 완료 아카이브 이동
|
## 효과
|
||||||
- #3 (시뮬레이터 이원화 해소 착수·문서 작성): **완료** — 착수 완료, 후속은 #28 통합 관리
|
- settings.json permissions와 동일 수준 + 시스템 경로 deny + 위험 Bash deny를 런타임에 직접 결정
|
||||||
- #12 (C17 신설): **완료 (실효)** — C17 폐기로 목적 소멸
|
- hook은 매 도구 호출 시 재실행되므로 스크립트 변경은 즉시 반영
|
||||||
- #17 (C17-3 보강): **완료 (실효)** — 상위 규칙 C17 폐기로 실효
|
- 단 hook 등록 자체(settings.json 변경)는 다음 세션부터 효력 발생
|
||||||
- #5 산출물 경로 정정 (구 경로 → 현 경로)
|
|
||||||
|
|
||||||
# [Live 변경분] 기획팀 PD 지시 로그 — 2026-04-17
|
## P19·P21 관련
|
||||||
|
- 전수 조사 4건 완료 아카이브 이동 (#3·#12·#17·기획 #27)
|
||||||
## 전수 조사 결과 1건 완료 아카이브 이동 (기획팀장 자체 수행)
|
- P21 5-A 단계 신설: 세션 갱신 시 활성 지시 실측 검증
|
||||||
- #27 (유니티 프로젝트 점검): **완료 아카이브 이동 완료**
|
|
||||||
- #3 (Phase 3 보류): 경로 정정, 보류 유지
|
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,17 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hooks": {
|
"hooks": {
|
||||||
|
"PreToolUse": [
|
||||||
|
{
|
||||||
|
"matcher": "",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "bash scripts/auto_approve.sh"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
"SessionStart": [
|
"SessionStart": [
|
||||||
{
|
{
|
||||||
"matcher": "",
|
"matcher": "",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
#!/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()
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# PreToolUse hook wrapper — auto_approve.py로 위임
|
||||||
|
# stdin JSON: {tool_name, tool_input}
|
||||||
|
# stdout JSON: {hookSpecificOutput: {permissionDecision: allow|deny|ask}}
|
||||||
|
|
||||||
|
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
|
||||||
|
[ -z "$REPO_ROOT" ] && REPO_ROOT="."
|
||||||
|
|
||||||
|
# Python 실행 (UTF-8 강제)
|
||||||
|
PYTHONIOENCODING=utf-8 python "$REPO_ROOT/scripts/auto_approve.py"
|
||||||
Loading…
Reference in New Issue