내 패턴 · 가드레일: 권한·승인·샌드박스

한 줄 요약

AI 에이전트가 무언가를 실행하기 직전에 “이거 해도 돼?”를 자동 판정하는 3중 잠금장치다. 왜 배우나: 한 겹이 뚫려도 다음 겹이 받쳐주는 “겹겹 방어”의 구조를 알아야, 내가 직접 안전장치를 설계하고 고칠 수 있다.

그림

flowchart TD
  A["모델: 도구 호출 결정"] --> B{"층위3: CLAUDE.md 규칙<br/>이미 컨텍스트에 주입됨"}
  B --> C["PreToolUse * : pre-tool-enforcer.mjs<br/>모든 도구 실행 전 게이트"]
  C -->|deny 게이트 매칭| X["permissionDecision: deny<br/>차단"]
  C -->|통과| D{"층위1: settings.allow/ask 대조"}
  D -->|ask 매칭| Q[사용자에게 질문]
  D -->|allow 매칭| Y[즉시 허용]
  D -->|"미매칭, Bash"| E["PermissionRequest:Bash<br/>permission-handler.mjs"]
  E -->|위험 셸문자 발견| Q
  E -->|"안전패턴 + 레포내 경로"| Y2["behavior: allow"]
  E -->|그 외| Q

쉽게 풀기

비유: 공항 보안 3단계. ① 탑승권의 사전 통과 도장(빠르지만 목록뿐) ② X-ray 검색대(가방을 실제로 열어 위험물 차단) ③ 게이트 앞 안내문(“라이터 금지” — 애초에 안 가져오게). 에이전트 가드레일도 이 세 겹이다.

층위정체비유 / 한계
① 정적 allowlistsettings.json의 “묻지 말고 통과” 화이트리스트 (Bash(git status:*))통과 도장 / 빠르지만 무딤
② 훅 동적 판정실행 직전 스크립트가 명령 내용을 읽어 판정X-ray / 똑똑하지만 우회 가능
③ 프롬프트 헌법CLAUDE.md·AGENTS.md의 자연어 금지 규칙안내문 / 강제력 없음

각 층은 작동 시점이 다르다. 한 층도 완벽하지 않아 셋을 겹친다 — 정적 목록은 무디고, 훅은 우회될 수 있고, 프롬프트는 강제력이 없다. 이 겹침이 이 챕터의 핵심이다.

flowchart LR
  P["③ 프롬프트 헌법<br/>결정 전 · 예방"] -.강제력 없음.-> A["① allowlist<br/>결정 직후 · 빠름"]
  A -.무딤.-> H["② 훅<br/>실행 직전 · 런타임 강제"]
  H -->|deny 사유 텍스트| P

왜 모델이 거부 못 하나? 훅은 모델이 거부할 수 없는 런타임 강제력(실행 차단)이고, 프롬프트는 모델의 판단을 형성할 뿐이다. 훅이 막을 때 내보내는 사유(permissionDecisionReason)는 다시 모델에게 피드백되어 다른 경로를 찾게 한다(예: model="sonnet"으로 재시도 안내).

핵심 정리

판정 출력 스키마(훅 stdout JSON) 체크포인트:

  • 허용: { continue: true, hookSpecificOutput: { hookEventName, decision: { behavior:'allow', reason } } }
  • 차단: permissionDecision: 'deny' (+ permissionDecisionReason)
  • 어느 키도 없음 → CC 기본 권한 흐름(=사용자에게 물음)

실제 예시

디스크의 실제 파일로 세 층위를 차례로 본다.

층위 1 — 정적 allowlist: settings.local.json"allow": ["Bash(sudo npm:*)"], settings.json"model": "opus[1m]" · enabledPlugins · skipDangerousModePermissionPrompt: true.

층위 2 — 훅 등록: PreToolUse(matcher *)는 pre-tool-enforcer.mjs(timeout 3s), PermissionRequest(matcher Bash)는 permission-handler.mjs(timeout 5s)를 건다.

층위 2의 판정 흐름은 두 단계 검사다 — 위험 셸문자를 먼저 전면 거부하고, 그 다음 안전 패턴 + 레포내 경로일 때만 자동 허용한다.

flowchart TD
  C[Bash 명령 수신] --> S{"위험 셸 메타문자?<br/>; & 파이프 $ 백틱 등"}
  S -->|있음| D["거부 → 기본 흐름"]
  S -->|없음| A{"ask 목록 매칭?"}
  A -->|예| D
  A -->|아니오| P{"SAFE_PATTERNS 매칭<br/>+ isSafeRepoPath?"}
  P -->|예| OK["behavior: allow"]
  P -->|아니오| D

층위 3 — 프로젝트 헌법: 실제 규칙들은 자연어 markdown으로 모델 컨텍스트에 박힌다.

<!-- /mnt/d/human-token-workflow/AGENTS.md (발췌) -->
1. 추론·창작 금지. 모든 주장은 원문 근거 필수.
2. 1차 자료 보존. 01_sources/ 원문 수정 금지, 파생물만 편집.
- 유료 보고서 우회 접근 금지 / 저작권 콘텐츠 전문 저장 금지 / frontmatter 없는 파일 저장 금지
 
<!-- /mnt/d/akh2/CLAUDE.md (발췌) -->
build/ ← 100% 파생물. 직접 편집 금지, 항상 재생성
파생값 추방: quality_score·registry_id·인덱스류는 페이지에 안 박음. build가 계산.
slug rename/삭제 직접 금지 — 신규 slug + supersedes 경유만 허용

직접 만들 때 템플릿

만들 때 점검표

  • allowlist *는 prefix 뒤 경계문자 보호(임의 접미사 허용 안 됨)
  • 훅은 fail-open — 에러/타임아웃 시 {continue:true}(가드 IO 실패가 작업을 영구 차단하면 안 됨)
  • 위험문자(; & | $ ` 등)는 패턴 매칭 전에 전면 거부
  • 경로 가드는 realpathSync로 심링크 해소 후 worktree 밖/민감경로(.env/.git/.ssh/secrets) 거부
  • askallow보다 우선하도록 순서 검사
  • CLAUDE.md에 권한 분리 규칙(writer≠reviewer) 명시
  • 훅 deny 사유에 다음 행동 안내를 넣어 모델이 우회 경로를 찾게 함

요약 & 셀프체크

3줄 요약

  1. 가드레일은 정적 allowlist(빠르지만 무딤) + 훅 동적 판정(똑똑하지만 우회 가능) + 프롬프트 헌법(강제력 없음)을 겹친 3중 잠금장치다.
  2. 작동 시점이 다르다 — 헌법은 결정 전, allowlist는 결정 직후, 훅은 실행 직전. 훅만이 모델이 거부할 수 없는 런타임 강제력을 갖는다.
  3. 위험 셸문자는 패턴 매칭보다 먼저 전면 거부, 경로는 심링크 해소 후 레포 밖·민감경로(.env/.git/.ssh/secrets)를 막는다. 훅은 fail-open이어야 한다.

스스로 답해보기

  • Bash(sudo npm:*)sudo npm install은 통과시키고 sudo npmfoo는 막는 이유는?
  • 훅이 실패했을 때 fail-open({continue:true})을 반환해야 하는 이유는? fail-closed면 어떤 문제가 생기나?
  • 같은 패턴이 allowask에 동시에 있으면 어느 쪽이 이기며, 그 순서가 왜 안전한가?

연결

MINE_개요 · _분석축_루브릭

Codex 교차검증

(원문 노트에 별도 Codex 교차검증 섹션이 없었음 — 재작성 시 신규 사실을 추가하지 않고 보존 차원에서 비워 둠. 추후 검증 결과가 생기면 이 콜아웃에 누적한다.)