claude-code/docs/ko/level-2-systems/permission-system.md
JuYoung Jeong 3358348196 docs: add comprehensive Korean source code analysis (19 documents, 6,926 lines)
Add complete Korean-language technical documentation analyzing Claude Code CLI
internals, organized in a leveled guide structure (Level 1-3 + Appendix).

Level 1 (입문): architecture overview, request lifecycle, key concepts
Level 2 (시스템): QueryEngine, Tool system, Command system, Permission system,
  Agent coordinator, UI/Ink components
Level 3 (심화): MCP/LSP integration, IDE bridge, Plugin/Skill system,
  Context compression, OAuth/Auth, Telemetry
Appendix: Korean-English glossary, file map, design patterns

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 19:39:03 +09:00

366 lines
17 KiB
Markdown

# 권한 시스템: Permission Model 분석
## 1. 개요
Permission System (권한 시스템)은 Claude Code에서 Tool (도구)을 실행하기 전 사용자의 승인을 관리하는 보안 레이어다. 단순한 yes/no 프롬프트가 아니라, 다계층 규칙 기반(rule-based) 평가 파이프라인으로 설계되어 있으며, 현재 모드(mode)·규칙(rule)·분류기(classifier)·훅(hook)이 순차적으로 적용된다.
**핵심 위치**
| 관심사 | 경로 |
|---|---|
| 타입 정의 | `src/types/permissions.ts` |
| Context 타입 | `src/Tool.ts` (`ToolPermissionContext`) |
| 모드 정의 | `src/utils/permissions/PermissionMode.ts` |
| 권한 체크 로직 | `src/utils/permissions/permissions.ts` |
| 초기화 및 모드 전환 | `src/utils/permissions/permissionSetup.ts` |
| 거부 추적 | `src/utils/permissions/denialTracking.ts` |
| Auto 모드 분류기 | `src/utils/permissions/yoloClassifier.ts` |
| 모드 순환 | `src/utils/permissions/getNextPermissionMode.ts` |
---
## 2. 아키텍처 다이어그램
```mermaid
flowchart TD
A[Tool 실행 요청] --> B{Permission Mode 확인}
B -->|bypassPermissions| Z[즉시 허용]
B -->|plan| P[쓰기 도구 차단 / 읽기만 허용]
B -->|default / acceptEdits / dontAsk / auto| C[규칙 매칭 단계]
C --> D{alwaysAllowRules 매칭?}
D -->|일치| Z
D -->|불일치| E{alwaysDenyRules 매칭?}
E -->|일치| DENY[거부 — PermissionDenyDecision]
E -->|불일치| F{alwaysAskRules 매칭?}
F -->|일치| ASK
F -->|불일치| G{Tool.checkPermissions 호출}
G -->|allow| Z
G -->|deny| DENY
G -->|ask| ASK
ASK --> H{shouldAvoidPermissionPrompts?}
H -->|true — 헤드리스 에이전트| HK[PermissionRequest Hook 실행]
HK -->|Hook 결정 있음| HOOKRESULT[Hook 결과 반환]
HK -->|Hook 결정 없음| DENY
H -->|false| I{auto 모드?}
I -->|yes| J[YOLO Classifier 실행]
J -->|shouldBlock=false| Z
J -->|shouldBlock=true| ASK2[사용자 프롬프트 표시]
I -->|no| ASK2
ASK2 --> K[사용자 결정]
K -->|허용| Z
K -->|거부| L[거부 기록 — DenialTracking]
L --> M{Denial 한계 도달?}
M -->|yes| N[프롬프트 폴백으로 전환]
M -->|no| DENY
Z --> EXEC[Tool 실행]
```
---
## 3. Permission Mode 타입
Permission Mode (권한 모드)는 시스템 전체의 기본 동작 방침을 결정한다. 외부(External)에서 사용자가 설정할 수 있는 모드와 내부(Internal) 전용 모드로 구분된다.
### 3.1 External Permission Mode
`src/types/permissions.ts`에서 `EXTERNAL_PERMISSION_MODES`로 선언되며, 사용자 설정(`settings.json`의 `defaultMode`)과 CLI 플래그(`--permission-mode`)에서 사용할 수 있다.
| 모드 | 심볼 | 색상 키 | 설명 |
|---|---|---|---|
| `default` | (없음) | `text` | 표준 동작. Tool별 `checkPermissions` 로직에 따라 ask/allow 결정 |
| `plan` | ⏸ | `planMode` | 읽기 전용 Plan Mode. 파일 쓰기·명령 실행 등 상태 변경 Tool 차단 |
| `acceptEdits` | ⏵⏵ | `autoAccept` | 파일 편집 자동 수락. Bash 실행은 여전히 확인 요구 |
| `bypassPermissions` | ⏵⏵ | `error` | 모든 권한 체크 우회. `isBypassPermissionsModeAvailable``true`일 때만 활성화 가능 |
| `dontAsk` | ⏵⏵ | `error` | 권한 프롬프트 없이 거부 대신 자동 허용. `bypassPermissions`와 유사하나 의미론적으로 구분 |
### 3.2 Internal Permission Mode
`auto` 모드는 `TRANSCRIPT_CLASSIFIER` feature flag가 활성화된 ant(내부) 빌드에서만 사용 가능하다. 외부 공개 모드 목록(`EXTERNAL_PERMISSION_MODES`)에는 포함되지 않으며, `toExternalPermissionMode()`를 통해 `'default'`로 매핑된다.
`bubble` 모드는 타입 정의에만 존재하며 현재 UI 순환에 노출되지 않는다.
### 3.3 모드 순환 (Shift+Tab)
`getNextPermissionMode()` 함수가 Shift+Tab 입력 시 다음 모드를 결정한다.
```
일반 사용자: default → acceptEdits → plan → [bypassPermissions →] [auto →] default
ant 내부: default → [bypassPermissions →] [auto →] default
```
`bypassPermissions``isBypassPermissionsModeAvailable``true`일 때만 순환에 포함되고, `auto``isAutoModeAvailable`과 런타임 feature gate가 모두 활성화되어야 포함된다.
---
## 4. ToolPermissionContext 분석
`ToolPermissionContext``src/Tool.ts`에서 `DeepImmutable<{...}>`로 정의된다. `DeepImmutable` (깊은 불변 타입) 래퍼가 적용되어 런타임 중 권한 컨텍스트가 외부에서 변조되는 것을 타입 수준에서 방지한다.
```typescript
export type ToolPermissionContext = DeepImmutable<{
mode: PermissionMode
additionalWorkingDirectories: Map<string, AdditionalWorkingDirectory>
alwaysAllowRules: ToolPermissionRulesBySource
alwaysDenyRules: ToolPermissionRulesBySource
alwaysAskRules: ToolPermissionRulesBySource
isBypassPermissionsModeAvailable: boolean
isAutoModeAvailable?: boolean
strippedDangerousRules?: ToolPermissionRulesBySource
shouldAvoidPermissionPrompts?: boolean
awaitAutomatedChecksBeforeDialog?: boolean
prePlanMode?: PermissionMode
}>
```
### 4.1 각 필드 설명
**`mode: PermissionMode`**
현재 활성 모드. 권한 평가의 첫 번째 분기점이다.
**`additionalWorkingDirectories: Map<string, AdditionalWorkingDirectory>`**
기본 작업 디렉터리 외에 파일 접근을 허용할 추가 경로 목록. 각 항목은 `path``source`(어떤 설정에서 추가되었는지)를 포함한다.
**`alwaysAllowRules / alwaysDenyRules / alwaysAskRules: ToolPermissionRulesBySource`**
세 가지 규칙 목록. 모두 `ToolPermissionRulesBySource` 타입으로, 규칙의 출처(source)별로 문자열 배열을 저장한다.
```typescript
type ToolPermissionRulesBySource = {
[T in PermissionRuleSource]?: string[]
}
```
출처(`PermissionRuleSource`)는 `userSettings`, `projectSettings`, `localSettings`, `flagSettings`, `policySettings`, `cliArg`, `command`, `session` 중 하나다. 이 출처 정보는 `createPermissionRequestMessage()`에서 사용자에게 "어느 설정 파일의 규칙이 이 요청을 차단했는지" 설명할 때 사용된다.
**`isBypassPermissionsModeAvailable: boolean`**
`bypassPermissions` 모드로 전환 가능한지 여부. 보안 민감 환경에서 `false`로 설정해 모드 접근 자체를 차단한다.
**`isAutoModeAvailable?: boolean`**
Auto 모드 게이트(Statsig feature gate 등)를 통과했는지 캐시된 값. 런타임에 live check(`isAutoModeGateEnabled()`)와 함께 `canCycleToAuto()`에서 이중 검증된다.
**`strippedDangerousRules?: ToolPermissionRulesBySource`**
Auto 모드 진입 시 제거된 위험 규칙의 원본 보관소. `isDangerousBashPermission()` 등이 탐지한 규칙들이 이 필드에 저장되며, 모드 이탈 시 복원에 사용된다.
**`shouldAvoidPermissionPrompts?: boolean`**
`true`이면 사용자 인터페이스를 띄울 수 없는 헤드리스(headless) 에이전트 환경임을 의미한다. 이 경우 ask 결정은 프롬프트 대신 `PermissionRequest` 훅을 거쳐 자동 거부된다.
**`awaitAutomatedChecksBeforeDialog?: boolean`**
`true`이면 사용자 다이얼로그 표시 전에 분류기·훅 등 자동화 체크가 완료될 때까지 대기한다. 코디네이터(coordinator) 워커 환경에서 활성화된다.
**`prePlanMode?: PermissionMode`**
모델이 자체적으로 Plan Mode에 진입하기 전의 원래 모드를 저장한다. Plan Mode 이탈 시 이 값으로 복원한다.
### 4.2 빈 컨텍스트 초기값
`getEmptyToolPermissionContext()`는 다음 기본값을 반환한다.
```typescript
{
mode: 'default',
additionalWorkingDirectories: new Map(),
alwaysAllowRules: {},
alwaysDenyRules: {},
alwaysAskRules: {},
isBypassPermissionsModeAvailable: false,
}
```
---
## 5. 권한 체크 흐름
### 5.1 규칙 매칭 단계
`permissions.ts``getAllowRules()`, `getDenyRules()`, `getAskRules()`는 각각 `PERMISSION_RULE_SOURCES` 순서(`userSettings` → `projectSettings``localSettings``flagSettings``policySettings``cliArg``command``session`)로 규칙을 평탄화(flatMap)하여 반환한다.
**Tool 수준 매칭 (`toolMatchesRule`)**
규칙에 `ruleContent`가 없으면 Tool 전체에 대한 매칭이다. MCP (Model Context Protocol) 도구는 `mcp__serverName__toolName` 형식으로 식별되며, 서버 수준 규칙(`mcp__server1`)은 해당 서버의 모든 도구에 적용된다.
**콘텐츠 수준 매칭 (`getRuleByContentsForTool`)**
`ruleContent`가 있는 경우 도구별 커스텀 매칭 로직(`Tool.checkPermissions()`)이 사용된다. 예를 들어 BashTool은 `Bash(git commit:*)` 형식으로 특정 명령 패턴에만 규칙을 적용한다.
### 5.2 복합 명령 처리
Bash 등 복합 명령을 실행하는 도구는 `subcommandResults` 타입의 `PermissionDecisionReason`을 반환할 수 있다. 하나의 명령 라인에 여러 서브커맨드가 있을 때, 각각을 독립적으로 평가한 결과를 맵으로 반환한다. 어느 하나라도 `ask` 또는 `passthrough`이면 전체가 사용자 프롬프트로 에스컬레이션된다.
### 5.3 PermissionDecision 타입 계층
```
PermissionResult
├── PermissionDecision
│ ├── PermissionAllowDecision — behavior: 'allow'
│ ├── PermissionAskDecision — behavior: 'ask', message, suggestions?, pendingClassifierCheck?
│ └── PermissionDenyDecision — behavior: 'deny', message, decisionReason (필수)
└── passthrough — 상위 처리기로 위임
```
`PermissionAskDecision``suggestions` 필드에는 사용자가 규칙을 영구 저장할 수 있는 `PermissionUpdate` 목록이 포함될 수 있다. 예를 들어 "이 명령을 항상 허용"을 클릭하면 `{type: 'addRules', destination: 'projectSettings', ...}` 형태의 업데이트가 실행된다.
---
## 6. 자동 모드 (Auto Mode)
### 6.1 게이트 접근 검증
Auto 모드는 다중 게이트 구조로 보호된다.
1. **Feature flag**: `TRANSCRIPT_CLASSIFIER` 빌드 플래그가 활성화된 빌드에서만 컴파일됨
2. **사용자 타입**: `process.env.USER_TYPE === 'ant'`인 내부 사용자 전용
3. **Statsig gate**: 런타임 feature gate 통과 필요 (`isAutoModeGateEnabled()`)
4. **모델 지원**: `modelSupportsAutoMode()`로 현재 메인 루프 모델이 auto 모드를 지원하는지 확인
5. **보안 제한 게이트**: `checkSecurityRestrictionGate()`
`verifyAutoModeGateAccess()` 함수가 세션 시작 시 위 조건들을 확인하고 결과를 `isAutoModeAvailable`에 캐시한다.
### 6.2 YOLO Classifier
`yoloClassifier.ts`의 YOLO (You Only Live Once) Classifier (분류기)는 Auto 모드에서 사용자 프롬프트 없이 도구 실행 여부를 자동 판단하는 LLM 기반 분류기다.
`YoloClassifierResult` 타입은 다음 주요 필드를 포함한다.
| 필드 | 설명 |
|---|---|
| `shouldBlock` | `true`이면 실행 차단 (사용자 프롬프트로 에스컬레이션) |
| `reason` | 판단 근거 |
| `unavailable` | API 오류 등으로 분류기 자체가 불가용한 경우 |
| `transcriptTooLong` | 컨텍스트 창 초과 — 동일 입력은 재시도해도 동일 결과이므로 즉시 폴백 |
| `model` | 분류에 사용된 모델 ID |
| `stage` | 2단계 XML 분류기에서 최종 결정을 낸 단계 (`'fast'` or `'thinking'`) |
분류기는 세션의 대화 기록(transcript)을 컨텍스트로 사용하며, `getLastClassifierRequests()`로 이전 요청을 캐싱하여 반복 오버헤드를 줄인다.
### 6.3 위험 권한 제거 (Dangerous Permission Stripping)
Auto 모드 진입 시 `isDangerousBashPermission()` 함수가 `alwaysAllowRules`를 스캔해 분류기를 우회할 수 있는 위험 패턴을 제거한다.
**위험으로 판단하는 패턴:**
```
Bash — 도구 전체 허용 (모든 명령 실행 가능)
Bash(*) — 와일드카드 전체 허용
Bash(python:*) — 스크립트 인터프리터 prefix 허용
Bash(python*) — 인터프리터 와일드카드
Bash(node -*) — 플래그 포함 패턴
```
제거된 규칙은 `strippedDangerousRules`에 보존되며, Auto 모드 이탈 시 복원된다. 동일 로직이 `isDangerousPowerShellPermission()` 함수로 PowerShell에도 적용된다.
### 6.4 Classifier Fail-Closed
분류기가 오류로 불가용 상태가 된 경우, 30분 새로고침 주기(`CLASSIFIER_FAIL_CLOSED_REFRESH_MS`)가 적용된다. 분류기 불가용 시 `buildClassifierUnavailableMessage()`가 메시지를 생성하고 ask 결정으로 폴백한다.
---
## 7. 거부 추적 (Denial Tracking)
`denialTracking.ts`는 분류기 또는 자동화된 체크가 반복적으로 거부할 때 사용자 프롬프트 폴백으로 전환하는 안전망이다.
### 7.1 상태 구조
```typescript
type DenialTrackingState = {
consecutiveDenials: number // 연속 거부 횟수
totalDenials: number // 총 거부 횟수
}
const DENIAL_LIMITS = {
maxConsecutive: 3, // 연속 3회 거부 시 폴백
maxTotal: 20, // 총 20회 거부 시 폴백
}
```
### 7.2 상태 전환
- `recordDenial(state)`: 거부 발생 시 두 카운터 모두 증가 (순수 함수, 새 객체 반환)
- `recordSuccess(state)`: 성공 시 `consecutiveDenials`를 0으로 리셋 (총 거부 수는 유지)
- `shouldFallbackToPrompting(state)`: 두 한계 중 하나라도 도달하면 `true` 반환
이 설계는 분류기가 특정 도구 요청을 지속적으로 잘못 판단하거나 과도하게 차단할 때 시스템이 무한 거부 루프에 빠지는 것을 방지한다.
---
## 8. 주요 설계 결정
### 8.1 DeepImmutable을 통한 불변성 보장
`ToolPermissionContext``DeepImmutable` 래퍼를 적용하는 이유는 권한 컨텍스트가 여러 계층의 도구 실행 코드에 전달될 때 의도치 않은 변경을 타입 수준에서 차단하기 위해서다. TypeScript의 표준 `readonly`는 최상위 속성만 보호하지만, `DeepImmutable`은 중첩 구조까지 재귀적으로 읽기 전용으로 만든다.
`src/types/permissions.ts`에는 import cycle (순환 임포트) 문제를 피하기 위해 단순화된 `readonly` 버전의 `ToolPermissionContext`가 별도로 정의되어 있다.
### 8.2 출처 추적 규칙 시스템
규칙을 단순 문자열 배열로 저장하지 않고 `ToolPermissionRulesBySource` 구조를 사용해 출처를 보존한다. 이를 통해:
- 사용자에게 "어느 설정 파일의 어떤 규칙이 이 요청을 제어하는지" 명시적으로 안내할 수 있다
- 프로젝트 설정과 사용자 설정의 규칙이 충돌할 때 우선순위를 관리할 수 있다
- 규칙 삭제 시 올바른 파일에서 제거할 수 있다 (`deletePermissionRuleFromSettings`)
### 8.3 Plan Mode 복원 메커니즘
모델이 대화 중 자체적으로 Plan Mode를 요청할 수 있다 (`prePlanMode` 필드). 이 경우 진입 전 모드를 `prePlanMode`에 저장하고 Plan Mode 이탈 시 복원한다. 이는 사용자가 수동으로 설정한 모드(`acceptEdits` 등)가 모델의 일시적 Plan Mode 진입으로 인해 유실되는 것을 방지한다.
### 8.4 헤드리스 에이전트를 위한 안전한 폴백
`shouldAvoidPermissionPrompts``true`인 환경(백그라운드 에이전트, CI 파이프라인 등)에서는 ask 결정이 사용자 UI를 띄우는 대신 `executePermissionRequestHooks()`를 거친다. 훅이 명시적 결정을 내리지 않으면 자동 거부(`AUTO_REJECT_MESSAGE`)로 처리된다. 이는 UI가 없는 환경에서 시스템이 무한 대기하거나 의도치 않게 위험한 작업을 승인하는 것을 방지하는 설계다.
---
## 9. 주요 타입 요약
```typescript
// 권한 행동 — 규칙이 취하는 조치
type PermissionBehavior = 'allow' | 'deny' | 'ask'
// 규칙 값 — 어떤 도구의 어떤 명령에 적용하는지
type PermissionRuleValue = {
toolName: string
ruleContent?: string // e.g. "git commit:*"
}
// 규칙 — 행동 + 값 + 출처
type PermissionRule = {
source: PermissionRuleSource
ruleBehavior: PermissionBehavior
ruleValue: PermissionRuleValue
}
// 업데이트 — 규칙 추가/제거/변경 명령
type PermissionUpdate =
| { type: 'addRules'; destination: PermissionUpdateDestination; rules: PermissionRuleValue[]; behavior: PermissionBehavior }
| { type: 'removeRules'; ... }
| { type: 'replaceRules'; ... }
| { type: 'setMode'; destination: ...; mode: ExternalPermissionMode }
| { type: 'addDirectories'; ... }
| { type: 'removeDirectories'; ... }
// 결정 이유 — 왜 이 결정이 내려졌는지
type PermissionDecisionReason =
| { type: 'rule'; rule: PermissionRule }
| { type: 'mode'; mode: PermissionMode }
| { type: 'hook'; hookName: string; reason?: string }
| { type: 'classifier'; classifier: string; reason: string }
| { type: 'subcommandResults'; reasons: Map<string, PermissionResult> }
| { type: 'workingDir'; reason: string }
| { type: 'safetyCheck'; reason: string; classifierApprovable: boolean }
| ...
```
---
## Navigation
- 이전: [Tool 시스템](tool-system.md)
- 다음: [에이전트 오케스트레이션](agent-coordinator.md)
- 상위: [목차](../README.md)