GitHub ↗
CHAPTER 04 OF 10
🔐

Permission System Design

권한 시스템 설계 — 결정론적 접근 제어

deny → ask → allow 평가 순서, exit 2 훅 블로킹 — 이 두 가지를 제대로 이해하면 에이전트 안전 설계의 80%가 해결된다.

Permission System Design cheatsheet
🍌 NANO BANANA CHEATSHEET · CH 04

Overview

개관

권한 시스템은 하네스의 '하드웨어 안전장치'다. CLAUDE.md가 소프트웨어 규칙(확률적)이라면, 권한 시스템은 운영체제 수준의 제한(결정론적)이다.

Claude Code의 권한 평가는 Deny-first 원칙을 따른다. deny 규칙이 있으면 다른 어떤 설정도 그것을 허용할 수 없다. 이것이 핵심이다. 'allow 규칙이 deny를 이기지 않는다' — 이 원칙을 모르면 아무리 복잡한 규칙을 써도 구멍이 생긴다.

동시에, 권한 시스템에는 중요한 한계가 있다. 파생 효과(악의적인 npm postinstall 스크립트), bash를 통한 직접 네트워크 접근(WebFetch 도구만 게이팅됨), API 호출로 트리거된 외부 작업 — 이런 것들은 권한 시스템으로 차단할 수 없다. 그래서 3계층 보안 모델(의도 + 인프로세스 + 아웃오브프로세스)이 필요하다.

🎯 Learning Goals
  • deny → ask → allow 평가 순서와 first-match 원칙을 설명할 수 있다
  • 6가지 permission mode의 차이와 적절한 사용 상황을 설명할 수 있다
  • Bash, Read, WebFetch, MCP 도구에 대한 규칙 문법을 작성할 수 있다
  • 3가지 운영 패턴(Approval-First, Curated Allow-List, Sandboxed Full-Auto)을 구분하여 적용할 수 있다

Sections

본문

평가 순서와 설정 우선순위

도구 호출 평가 순서 (결정론적, 첫 매칭 승리):

1. deny 규칙 → 매칭되면 즉시 차단
2. ask 규칙 → 매칭되면 사용자에게 확인 요청
3. allow 규칙 → 매칭되면 자동 허가
4. defaultMode → 위 중 아무것도 없으면 기본 동작

Specificity는 순서를 이기지 않는다. deny ["Bash(*)"]allow ["Bash(npm run test)"]가 동시에 있으면, allow가 더 구체적이라도 deny가 이긴다. 이것을 모르면 의도치 않은 차단이 발생한다.

설정 파일 우선순위 (높은 것 → 낮은 것):

  1. Managed settings (조직 배포, 오버라이드 불가)
  2. CLI arguments (--allowedTools, --disallowedTools)
  3. Local project settings (.claude/settings.local.json)
  4. Shared project settings (.claude/settings.json)
  5. User settings (~/.claude/settings.json)

중요: 어느 레벨에서든 도구가 deny되면 다른 레벨에서 allow할 수 없다.

6가지 Permission Mode

Mode 설명 사용 상황
default 각 도구 첫 사용 시 확인 요청 일반 개발
acceptEdits 파일 편집과 일반 파일시스템 명령 자동 승인 빠른 코딩 세션
plan 읽기 전용 탐색만, 소스 편집 불가 코드 리뷰, 분석
auto ML 분류기가 자동 승인/차단 (리서치 프리뷰) 실험적
dontAsk 사전 승인된 것만 허용, 나머지 자동 거부 높은 신뢰 환경
bypassPermissions 명시적 ask 규칙 외 모든 확인 건너뜀 컨테이너/VM 전용!

bypassPermissions 경고: 이 모드는 반드시 컨테이너나 VM 안에서만 사용해야 한다. 베어 호스트 OS에서 bypassPermissions를 사용하는 것은 지금까지 발생한 주요 사고들의 공통 원인이다.

Rule Syntax 완전 가이드

{
  "permissions": {
    "allow": [
      "Bash(npm run *)",           // npm run으로 시작하는 모든 명령
      "Bash(git commit *)",        // git commit 허용
      "Bash(* --version)",         // --version 플래그 가진 모든 명령
      "Read(./.env)",              // 특정 .env 파일 읽기 허용
      "WebFetch(domain:github.com)",// github.com 도메인만
      "WebFetch(domain:*.example.com)",// 서브도메인 포함
      "mcp__puppeteer__*",         // puppeteer MCP 모든 도구
      "Agent(Explore)"             // Explore 서브에이전트 타입
    ],
    "deny": [
      "Bash(git push --force *)",   // force push 금지
      "Bash(rm -rf *)",            // 재귀 삭제 금지
      "Read(~/.ssh/*)",            // SSH 키 읽기 금지
      "Read(.env*)",               // .env 파일 읽기 금지
      "mcp__*"                     // 모든 MCP 도구 금지
    ],
    "ask": [
      "Bash(git push *)",          // push는 확인 요청
      "Bash(terraform apply *)",   // terraform apply 확인
      "Bash(rm *)",               // rm은 확인
      "Bash(git reset --hard *)"
    ]
  }
}

중요 문법 규칙:

  • 공백이 중요: Bash(ls *) = ls 뒤에 공백+인수. Bash(ls*) = ls로 시작하는 것 (lsof 포함)
  • 복합 명령 (&&, ||, ;, |): 각 서브명령이 독립적으로 평가됨
  • 프로세스 래퍼(timeout, nice, nohup 등)는 매칭 전 제거됨
  • 읽기 전용 명령(ls, cat, grep, find 등)은 자동 승인

3가지 운영 패턴

Pattern A: Approval-First (프로덕션 저장소)

{
  "permissions": {
    "ask": ["Bash(*)", "Write(*)"],
    "deny": ["Bash(rm -rf *)", "Bash(git push --force *)", "Bash(sudo *)"],
    "defaultMode": "dontAsk"
  }
}
  • 가장 작은 blast radius
  • 프로덕션 자격증명, 공유 인프라에 적합

Pattern B: Curated Allow-List (팀/개인 프로젝트)

{
  "permissions": {
    "allow": ["Read(*)", "Bash(npm run *)", "Bash(git status)", "Bash(git log *)", "Bash(git commit *)", "Bash(git diff *)"],
    "ask": ["Bash(git push *)", "Bash(git reset *)"],
    "deny": ["Bash(git push --force *)", "Bash(rm -rf *)"]
  }
}
  • 속도와 안전의 균형
  • 일반 개발 팀에 권장

Pattern C: Sandboxed Full-Auto (배치 작업) — 컨테이너 필수!

{
  "permissions": {
    "defaultMode": "bypassPermissions"
  }
}
FROM debian:stable-slim
RUN useradd --create-home claudebot
USER claudebot  # 절대 root로 실행 금지
WORKDIR /workspace
# ~/.ssh, ~/.aws, ~/.config 마운트 절대 금지
💡 Analogy · 비유
은행 금고 시스템

권한 시스템을 은행 금고 시스템으로 생각해보자.

Deny 규칙은 금고 잠금장치 — 열쇠가 있어도 특정 시간대에는 절대 열 수 없다. Ask 규칙은 이중 서명 필요 금고 — 한 명이 시도하면 다른 관리자의 승인이 필요하다. Allow 규칙은 일반 창구 — 신분증 한 번 확인하면 자동으로 처리된다.

핵심: 금고 잠금장치(Deny)는 창구 직원(Allow)이 아무리 처리하려 해도 막는다. 이것이 'Deny가 Allow를 이긴다'는 원칙의 실체다.

bypassPermissions는 금고 마스터 키 — 모든 잠금이 풀린다. 이 키는 반드시 자물쇠 달린 별도 공간(컨테이너/VM)에서만 사용해야 한다. 열린 공간에서 마스터 키를 쓰다가 사고가 난다.

현재 permission 설정을 분석하고 취약점을 찾아주는 도구. settings.json을 읽어서 잠재적 보안 문제를 리포트한다.

python
#!/usr/bin/env python3
"""permission-audit.py — permission 설정 취약점 분석"""
import json
from pathlib import Path

def audit_permissions(settings_path: str = "~/.claude/settings.json"):
    path = Path(settings_path).expanduser()
    if not path.exists():
        print(f"❌ {settings_path} 없음")
        return
    
    settings = json.loads(path.read_text())
    perms = settings.get('permissions', {})
    allow = perms.get('allow', [])
    deny = perms.get('deny', [])
    ask = perms.get('ask', [])
    mode = settings.get('defaultMode', 'default')
    
    issues = []
    recommendations = []
    
    # 위험: bypassPermissions
    if mode == 'bypassPermissions':
        issues.append("🚨 CRITICAL: defaultMode=bypassPermissions — 컨테이너 안에서만 허용")
    
    # 경고: 광범위한 Bash allow
    if 'Bash(*)' in allow or 'Bash' in allow:
        issues.append("⚠️  Bash(*) 전체 허용 — 최소 deny 리스트라도 추가하세요")
    
    # 경고: SSH/AWS 자격증명 보호 여부
    ssh_protected = any('ssh' in d.lower() for d in deny)
    aws_protected = any('aws' in d.lower() or 'credentials' in d.lower() for d in deny)
    if not ssh_protected:
        recommendations.append("💡 권장: deny에 Read(~/.ssh/**) 추가")
    if not aws_protected:
        recommendations.append("💡 권장: deny에 Read(~/.aws/credentials) 추가")
    
    # 경고: rm -rf 보호 여부  
    rm_protected = any('rm -rf' in d for d in deny)
    if not rm_protected:
        recommendations.append("💡 권장: deny에 Bash(rm -rf *) 추가")
    
    # 경고: force push 보호 여부
    force_push_protected = any('force' in d for d in deny)
    if not force_push_protected:
        recommendations.append("💡 권장: deny에 Bash(git push --force *) 추가")
    
    # 정보 출력
    print(f"=== Permission Audit: {settings_path} ===")
    print(f"Mode: {mode}")
    print(f"Allow rules: {len(allow)}, Deny rules: {len(deny)}, Ask rules: {len(ask)}")
    
    if issues:
        print("\n🚨 Issues:")
        for i in issues:
            print(f"  {i}")
    
    if recommendations:
        print("\n💡 Recommendations:")
        for r in recommendations:
            print(f"  {r}")
    
    if not issues and not recommendations:
        print("\n✅ 기본 보안 검사 통과")

if __name__ == '__main__':
    import sys
    path = sys.argv[1] if len(sys.argv) > 1 else "~/.claude/settings.json"
    audit_permissions(path)

settings.json을 읽어서 bypassPermissions 사용, 광범위한 Bash 허용, SSH/AWS 자격증명 보호 여부, rm -rf와 force push 보호 여부를 체크한다. 팀의 settings.json에 대해 CI에서 실행하면 자동 보안 감사가 된다.

🏭 현업에서의 평가
시니어 엔지니어들은 'Claude가 실수로 데이터를 지웠어요'라는 말을 들으면 먼저 permission 설정을 묻는다. 권한 시스템이 제대로 설계되어 있으면 에이전트의 실수가 사고가 되지 않는다.

✅ 시니어가 보는 것

  • deny 규칙이 최소한 rm -rf, git push --force, sudo를 포함하는가
  • SSH 키, AWS 자격증명, .env 파일에 대한 Read deny가 있는가
  • bypassPermissions가 컨테이너 밖에서 사용되지 않는가
  • settings.json이 git에 커밋되어 팀 전체에 적용되는가

⚠️ 레드 플래그

  • 권한 설정 없음 (default 모드만)
  • exit 1을 쓰는 훅 (exit 2만 블로킹됨)
  • bypassPermissions를 베어 호스트에서 사용
  • Bash(*) allow + 아무 deny 없음

🎤 예상 인터뷰 질문

  1. allow ["Bash(npm run test)"]와 deny ["Bash(*)"]가 동시에 있을 때 npm run test는 실행되는가?
  2. PreToolUse 훅에서 exit 1과 exit 2의 차이는?
  3. bypassPermissions 모드를 안전하게 사용할 수 있는 유일한 조건은?
숙달 vs 익숙함: 단순히 아는 사람은 deny 리스트를 만든다. 마스터한 사람은 Pattern A/B/C를 상황에 따라 전환하고, 조직 설정(Managed)으로 팀 전체에 배포하고, 토큰 스코핑(read-only GitHub 토큰 vs. write 토큰)을 rule 설정보다 먼저 고려한다.

Key Takeaways

핵심 정리

Deny-first

deny → ask → allow 순서. Deny가 있으면 Allow가 아무리 구체적이어도 차단된다.

First-match wins

Specificity가 순서를 이기지 않는다. 더 구체적인 Allow도 앞에 있는 Deny에게 진다.

5개 설정 파일 우선순위

Managed(조직) > CLI args > settings.local.json > settings.json > ~/.claude/settings.json

exit 2만 블로킹

훅에서 exit 1은 Unix 관례이지만 Claude Code는 무시. exit 2만 블로킹한다. 이것이 가장 흔한 실수다.

bypassPermissions = 컨테이너 전용

이 모드는 절대 베어 호스트 OS에서 사용 금지. 컨테이너 + 비root 사용자 + 홈디렉토리 비마운트.

토큰 스코핑 > 규칙 스코핑

read-only GitHub 토큰은 어떤 allow 규칙보다 근본적으로 안전하다. 규칙 전에 토큰 권한을 먼저 설계하라.

3가지 패턴

Approval-First(프로덕션), Curated Allow-List(팀 개발), Sandboxed Full-Auto(배치+컨테이너).

자동 승인 명령들

ls, cat, echo, pwd, head, tail, grep, find, wc, which, diff — 이것들은 이미 자동 승인된다. deny 할 이유 없음.