权限与审批系统

📑 目录

在前面的文章中,我们解析了 Claude Code 的命令解析、输入处理和状态管理。本文将深入探讨 Claude Code 最为关键的安全基础设施——权限与审批系统(Permission & Approval System)。作为一个能够在用户本地执行 Shell 命令、读写文件、发起网络请求的 AI Agent,Claude Code 必须在"智能自动化"与"安全可控"之间找到精确的平衡点。这套权限系统就是维持这一平衡的核心机制。

一、权限架构总览

Claude Code 的权限系统采用了分层规则 + 多模式控制的架构设计。整个权限模块分布在两个核心目录中:

  • src/types/permissions.ts(约 13KB):纯类型定义层,打破循环依赖
  • src/utils/permissions/:24 个实现文件,涵盖规则解析、权限检查、分类器、状态管理等
  • src/components/permissions/:40+ 个 UI 组件,负责各类审批对话框的渲染与交互
flowchart TD
    subgraph TypeLayer["类型定义层"]
        PT[src/types/permissions.ts]
    end
    
    subgraph UtilsLayer["权限逻辑层"]
        PERM[src/utils/permissions/permissions.ts]
        RULE[src/utils/permissions/PermissionRule.ts]
        RESULT[src/utils/permissions/PermissionResult.ts]
        SHELL[src/utils/permissions/shellRuleMatching.ts]
        FS[src/utils/permissions/filesystem.ts]
        CLASS[src/utils/permissions/classifierDecision.ts]
        YOLO[src/utils/permissions/yoloClassifier.ts]
        DENIAL[src/utils/permissions/denialTracking.ts]
    end
    
    subgraph UILayer["审批 UI 层"]
        PREQ[src/components/permissions/PermissionRequest.tsx]
        PDIA[src/components/permissions/PermissionDialog.tsx]
        BPREQ[src/components/permissions/BashPermissionRequest/]
        FPREQ[src/components/permissions/FileEditPermissionRequest/]
    end
    
    PT --> PERM
    PT --> RULE
    PT --> RESULT
    PERM --> SHELL
    PERM --> FS
    PERM --> CLASS
    PERM --> YOLO
    PERM --> DENIAL
    PERM --> PREQ
    PREQ --> PDIA
    PREQ --> BPREQ
    PREQ --> FPREQ

1.1 权限模式(Permission Mode)

Claude Code 定义了 7 种权限模式,按安全等级从低到高排列:

模式说明安全等级
bypassPermissions绕过所有权限检查⚠️ 最低
dontAsk静默拒绝所有需要审批的操作
auto自动模式,由 AI 分类器决定是否允许
default默认模式,每次需要审批时弹出提示
acceptEdits自动接受工作目录内的文件编辑中高
plan计划模式,所有操作需要显式确认
bubble内部调试用-

这些模式定义在 src/types/permissions.ts 第 15~33 行:

export const EXTERNAL_PERMISSION_MODES = [
  'acceptEdits',
  'bypassPermissions',
  'default',
  'dontAsk',
  'plan',
] as const

export type ExternalPermissionMode = (typeof EXTERNAL_PERMISSION_MODES)[number]
export type InternalPermissionMode = ExternalPermissionMode | 'auto' | 'bubble'

用户可通过 Shift+Tab 在部分模式之间循环切换,切换逻辑由 src/utils/permissions/getNextPermissionMode.ts 第 23~72 行实现。对于 Ant 内部用户,acceptEditsplan 被跳过,直接提供 autobypassPermissions 选项。

1.2 权限行为三元组

每条权限规则的行为只能是三种之一(src/types/permissions.ts 第 39 行):

export type PermissionBehavior = 'allow' | 'deny' | 'ask'
  • allow:无条件允许该工具/操作执行
  • deny:无条件拒绝该工具/操作执行
  • ask:强制弹出审批对话框,要求用户确认

1.3 权限规则的结构

权限规则由来源(Source)、行为(Behavior)和值(Value)三部分组成(src/types/permissions.ts 第 48~62 行):

export type PermissionRuleSource =
  | 'userSettings' | 'projectSettings' | 'localSettings'
  | 'flagSettings' | 'policySettings' | 'cliArg' | 'command' | 'session'

export type PermissionRuleValue = {
  toolName: string
  ruleContent?: string
}

export type PermissionRule = {
  source: PermissionRuleSource
  ruleBehavior: PermissionBehavior
  ruleValue: PermissionRuleValue
}

ruleContent 是可选的精细化匹配内容。例如 Bash(prefix:*) 表示匹配以 prefix 开头的 Bash 命令,而 Bash(无 ruleContent)则表示匹配所有 Bash 命令。

二、分类审批机制

Claude Code 对不同类型的操作实施了差异化的审批策略。每种工具都有独立的 checkPermissions() 方法,实现了细粒度的安全检查。

2.1 文件读写权限

文件操作是最频繁也是风险最高的操作之一。文件权限检查集中在 src/utils/permissions/filesystem.ts 和各个文件工具的 checkPermissions 方法中。

敏感文件保护src/utils/permissions/filesystem.ts 第 37~57 行):

export const DANGEROUS_FILES = [
  '.gitconfig', '.gitmodules', '.bashrc', '.bash_profile',
  '.zshrc', '.zprofile', '.profile', '.ripgreprc',
  '.mcp.json', '.claude.json',
] as const

export const DANGEROUS_DIRECTORIES = [
  '.git', '.vscode', '.idea', '.claude',
] as const

对这些敏感路径的编辑会触发 safetyCheck 类型的审批要求,且即使在 bypassPermissions 模式下也无法绕过src/utils/permissions/permissions.ts 第 1273~1283 行)。这是系统最后的防线之一。

审批粒度

  • 路径级:检查操作是否在允许的工作目录内
  • 工具级FileReadToolFileWriteToolFileEditTool 各自有独立的审批规则
  • 内容级:Notebook 编辑、Sed 替换等特殊操作有额外的安全校验

2.2 Shell 执行权限

Shell 命令的审批是权限系统中最复杂的部分,涉及多层检查机制。

规则匹配语法src/utils/permissions/shellRuleMatching.ts 第 22~47 行):

export type ShellPermissionRule =
  | { type: 'exact'; command: string }
  | { type: 'prefix'; prefix: string }
  | { type: 'wildcard'; pattern: string }
  • 精确匹配npm install 只匹配该命令
  • 前缀匹配npm:* 匹配所有以 npm 开头的命令
  • 通配符匹配npm * 使用 * 匹配任意字符序列(支持转义 \* 匹配字面星号)

危险命令检测src/utils/permissions/permissionSetup.ts 第 82~137 行的 isDangerousBashPermission() 函数会阻止用户配置过于宽泛的 Bash 权限规则。例如 python:*node:* 等解释器前缀规则在 auto 模式下会被标记为危险并移除,防止分类器被绕过。

沙箱自动授权:当 SandboxManager.isAutoAllowBashIfSandboxedEnabled() 启用且命令将在沙箱中执行时,部分审批可被自动跳过(src/utils/permissions/permissions.ts 第 1193~1207 行)。

2.3 网络请求权限

网络请求通过 WebFetchTool 实现,其审批对话框为 WebFetchPermissionRequest。网络权限的粒度相对粗粒度:

  • 工具级:允许/拒绝所有 WebFetchTool 调用
  • URL 级:可在规则中指定特定 URL 前缀或模式

与本地文件操作不同,网络请求没有 safetyCheck 级别的硬保护,完全依赖规则系统和审批对话框。

2.4 MCP Server 权限

MCP(Model Context Protocol)工具具有特殊的权限命名空间。在 src/utils/permissions/permissions.ts 第 233~261 行的 toolMatchesRule() 函数中,MCP 工具的权限匹配逻辑如下:

const ruleInfo = mcpInfoFromString(rule.ruleValue.toolName)
const toolInfo = mcpInfoFromString(nameForRuleMatch)

return (
  ruleInfo !== null &&
  toolInfo !== null &&
  (ruleInfo.toolName === undefined || ruleInfo.toolName === '*') &&
  ruleInfo.serverName === toolInfo.serverName
)

这意味着:

  • 规则 mcp__server1 可授权 Server1 的所有工具
  • 规则 mcp__server1__* 同样匹配 Server1 的所有工具
  • 规则 mcp__server1__tool1 仅匹配特定工具

MCP 工具的权限审批通过通用的 PermissionRequest 机制处理,没有单独的 MCP 特殊审批对话框。

三、审批对话框组件

当权限检查返回 ask 行为时,系统会渲染对应的审批对话框。所有对话框组件位于 src/components/permissions/ 目录。

3.1 组件架构

flowchart LR
    A[PermissionRequest.tsx] --> B[PermissionDialog.tsx]
    A --> C[PermissionPrompt.tsx]
    A --> D[BashPermissionRequest]
    A --> E[FileWritePermissionRequest]
    A --> F[FileEditPermissionRequest]
    A --> G[WebFetchPermissionRequest]
    A --> H[FallbackPermissionRequest.tsx]
    
    B --> I[PermissionRequestTitle.tsx]
    B --> J[WorkerBadge.tsx]
    
    C --> K[Select 组件]
    C --> L[Feedback 输入框]
    
    D --> M[PermissionRuleExplanation.tsx]
    D --> N[PermissionExplanation.tsx]

核心组件职责

  • PermissionRequest.tsx(第 78~128 行):入口分发器,根据工具类型选择对应的审批组件
  • PermissionDialog.tsx:统一的对话框容器,提供边框、标题、工作区徽章等通用布局
  • PermissionPrompt.tsx:通用选项提示组件,处理 "允许/拒绝/总是允许/总是拒绝" 等标准交互
  • BashPermissionRequest/:最复杂的 Shell 审批组件,集成分类器状态显示、破坏性命令警告、规则解释等

3.2 Bash 审批对话框的特殊设计

BashPermissionRequestsrc/components/permissions/BashPermissionRequest/BashPermissionRequest.tsx)是审批 UI 中最复杂的组件,因为它需要处理:

  1. Sed 编辑检测:如果命令是 sed 替换操作,自动跳转到 SedEditPermissionRequest
  2. 分类器检查动画:当 Bash Classifier 正在评估命令安全性时,显示闪烁的 "Attempting to auto-approve…" 提示(ClassifierCheckingSubtitle 组件)
  3. 破坏性命令警告:集成 getDestructiveCommandWarning(),对 rmdrop table 等危险操作加红警示
  4. 权限解释器:通过 PermissionExplanation.tsx 提供按 Ctrl+E 触发的 AI 生成的风险解释(低/中/高风险分级)

3.3 用户交互与记住选择

当用户在审批对话框中做出选择时,系统会将选择转化为 PermissionUpdate 并持久化。更新操作定义在 src/types/permissions.ts 第 68~106 行:

export type PermissionUpdate =
  | { type: 'addRules'; destination: PermissionUpdateDestination; rules: PermissionRuleValue[]; behavior: PermissionBehavior }
  | { type: 'replaceRules'; destination: PermissionUpdateDestination; rules: PermissionRuleValue[]; behavior: PermissionBehavior }
  | { type: 'removeRules'; destination: PermissionUpdateDestination; rules: PermissionRuleValue[]; behavior: PermissionBehavior }
  | { type: 'setMode'; destination: PermissionUpdateDestination; mode: ExternalPermissionMode }
  | { type: 'addDirectories'; destination: PermissionUpdateDestination; directories: string[] }
  | { type: 'removeDirectories'; destination: PermissionUpdateDestination; directories: string[] }

用户可以选择:

  • Allow once:仅允许本次执行
  • Always allow:添加 allow 规则到会话或设置
  • Deny:拒绝本次执行
  • Always deny:添加 deny 规则

"记住选择"通过 applyPermissionUpdate()persistPermissionUpdates() 两个步骤完成:先更新内存中的权限上下文,再写入到对应的设置文件(用户设置、项目设置或本地设置)。

3.4 权限规则解释器

PermissionRuleExplanation.tsx 负责向用户展示为什么某个操作需要审批。它支持多种决策原因类型的文本生成(第 22~73 行):

  • rule:显示触发审批的具体规则及其来源配置
  • hook:显示 PreToolUse Hook 拦截的原因
  • safetyCheck:显示敏感路径或安全检查失败的原因
  • classifier:显示分类器(bashauto-mode)的拦截原因
  • mode:显示当前权限模式导致的审批要求

四、Bypass 模式与安全阀

bypassPermissions 是权限系统中最激进的模式,它跳过大部分审批检查,让用户获得近乎无限制的自动化体验。但这也带来了最大的安全风险。

4.1 Bypass 何时生效

src/utils/permissions/permissions.ts 第 1285~1299 行,bypassPermissions 模式的生效逻辑如下:

const shouldBypassPermissions =
  appState.toolPermissionContext.mode === 'bypassPermissions' ||
  (appState.toolPermissionContext.mode === 'plan' &&
    appState.toolPermissionContext.isBypassPermissionsModeAvailable)

if (shouldBypassPermissions) {
  return {
    behavior: 'allow',
    updatedInput: getUpdatedInputOrFallback(toolPermissionResult, input),
    decisionReason: { type: 'mode', mode: appState.toolPermissionContext.mode },
  }
}

注意:即使 bypass 模式也无法绕过以下检查

  1. Deny 规则(第 1167~1177 行):显式拒绝规则始终优先
  2. Safety Check(第 1273~1283 行):.git/.claude/ 等敏感路径的编辑仍需审批
  3. 显式 Ask 规则(第 1257~1271 行):用户配置的 ask 规则仍会被尊重
  4. 需要用户交互的工具(第 1245~1255 行):如 AskUserQuestionTool 等必须人工介入的工具

4.2 Bypass Kill Switch

为了防止 bypass 模式在组织层面被滥用,Claude Code 实现了 Kill Switch 机制(src/utils/permissions/bypassPermissionsKillswitch.ts)。

该模块通过 checkAndDisableBypassPermissionsIfNeeded() 在会话启动时检查 GrowthBook Feature Gate shouldDisableBypassPermissions。如果组织策略禁用了 bypass 模式,系统会自动将用户的权限模式降级为 default,且 isBypassPermissionsModeAvailable 被置为 false

export async function checkAndDisableBypassPermissionsIfNeeded(
  toolPermissionContext: ToolPermissionContext,
  setAppState: (f: (prev: AppState) => AppState) => void,
): Promise<void> {
  if (bypassPermissionsCheckRan) return
  bypassPermissionsCheckRan = true
  if (!toolPermissionContext.isBypassPermissionsModeAvailable) return

  const shouldDisable = await shouldDisableBypassPermissions()
  if (!shouldDisable) return

  setAppState(prev => ({
    ...prev,
    toolPermissionContext: createDisabledBypassPermissionsContext(prev.toolPermissionContext),
  }))
}

此检查只运行一次,并在用户重新登录(/login)时通过 resetBypassPermissionsCheck() 重置,确保组织策略变更能及时生效。

4.3 进入 Auto 模式时的权限清理

当用户从 bypassPermissions 切换到 auto 模式时,permissionSetup.ts 中的 transitionPermissionMode() 会自动剥离危险权限规则。这包括:

  • 过于宽泛的 Bash 规则(如 Bash 无内容、python:*
  • 危险的 PowerShell 规则(如 iex 相关模式)
  • 不受管理的权限规则(如果组织策略要求仅允许托管规则)

五、权限检查链路

工具调用前的权限检查是整个系统的核心安全链路。主入口函数 hasPermissionsToUseTool 位于 src/utils/permissions/permissions.ts 第 473 行,它是一个超过 500 行的复杂函数, orchestrates 了完整的审批决策流程。

5.1 检查链路流程图

flowchart TD
    Start([工具调用请求]) --> Step1[Step 1: 规则检查]
    
    Step1 --> Step1a[1a. Deny规则匹配?]
    Step1a -->|是| Deny1[返回 deny]
    Step1a -->|否| Step1b[1b. Ask规则匹配?]
    
    Step1b -->|是| Step1bSandbox[沙箱可自动授权?]
    Step1bSandbox -->|是| Step1c
    Step1bSandbox -->|否| Ask1[返回 ask]
    Step1b -->|否| Step1c[1c. 工具checkPermissions]
    
    Step1c --> Step1d[1d. 工具拒绝?]
    Step1d -->|是| Deny2[返回 deny]
    Step1d -->|否| Step1e[1e. 需要用户交互?]
    
    Step1e -->|是且ask| Ask2[返回 ask]
    Step1e -->|否| Step1f[1f. 显式Ask规则?]
    
    Step1f -->|是| Ask3[返回 ask]
    Step1f -->|否| Step1g[1g. SafetyCheck?]
    
    Step1g -->|是| Ask4[返回 ask
bypass也无法跳过] Step1g -->|否| Step2[Step 2: 模式检查] Step2 --> Step2a[2a. bypassPermissions?] Step2a -->|是| Allow1[返回 allow] Step2a -->|否| Step2b[2b. AlwaysAllow规则?] Step2b -->|是| Allow2[返回 allow] Step2b -->|否| Step3[Step 3: 模式转换] Step3 --> Step3a[dontAsk模式?] Step3a -->|是| Deny3[返回 deny] Step3a -->|否| Step3b[auto/plan模式?] Step3b -->|是| Classifier[运行分类器] Classifier -->|blocked| Deny4[返回 deny] Classifier -->|allowed| Allow3[返回 allow] Classifier -->|unavailable| Ask5[回退到 ask] Step3b -->|否| Step3c[headless代理?] Step3c -->|是| Hook[运行Permission Hook] Hook -->|有决策| ReturnHook[返回 hook 决策] Hook -->|无决策| Deny5[返回 deny] Step3c -->|否| Ask6[返回 ask
弹出审批对话框]

5.2 内层检查:hasPermissionsToUseToolInner

hasPermissionsToUseToolInnerpermissions.ts 第 1158~1317 行)是链路的核心,它按严格顺序执行以下检查:

Step 1 — 规则与工具检查(不可被 bypass):

  1. 1a Deny 规则:全局 deny 规则最先检查,立即拒绝
  2. 1b Ask 规则:全局 ask 规则,除非沙箱可自动授权
  3. 1c 工具自定义检查:调用 tool.checkPermissions(parsedInput, context)
  4. 1d 工具拒绝:工具层面拒绝(如 Bash 子命令规则不匹配)
  5. 1e 用户交互需求tool.requiresUserInteraction?.() 为真时必须人工介入
  6. 1f 内容级 Ask 规则:如 Bash(npm publish:*) 这种精细化 ask 规则
  7. 1g Safety Check:敏感路径安全检查,bypass 免疫

Step 2 — 模式级快速通道

  • 2a bypassPermissions:如果模式为 bypass 且通过 Step 1,直接允许
  • 2b Always Allow 规则:全局 allow 规则,如 Bash 授权所有命令

Step 3 — 结果转换

  • passthrough 转换为 ask
  • 返回带有建议(suggestions)的决策结果

5.3 外层包装:hasPermissionsToUseTool

hasPermissionsToUseTool 在内层结果基础上增加了模式相关的转换逻辑(permissions.ts 第 473~640 行):

dontAsk 模式转换
所有 ask 决策被强制转换为 deny,并附带拒绝消息:

if (appState.toolPermissionContext.mode === 'dontAsk') {
  return {
    behavior: 'deny',
    decisionReason: { type: 'mode', mode: 'dontAsk' },
    message: DONT_ASK_REJECT_MESSAGE(tool.name),
  }
}

auto 模式分类器
当处于 autoplan 模式且 autoModeStateModule.isAutoModeActive() 为真时,系统不会立即弹出对话框,而是将决策委托给 YOLO ClassifieryoloClassifier.ts)。

分类器的调用流程:

  1. 首先尝试 acceptEdits 快速通道:如果该操作在 acceptEdits 模式下会被允许,则跳过分类器(减少 API 调用开销)
  2. 检查安全工具白名单(classifierDecision.ts 第 64~98 行的 SAFE_YOLO_ALLOWLISTED_TOOLS):只读工具如 FileReadToolGrepToolGlobTool 等直接允许
  3. 调用 classifyYoloAction() 发送当前会话上下文和工具调用描述给分类器模型
  4. 根据分类器返回的 shouldBlock 决定允许或拒绝

Denial Tracking 回退机制
当分类器连续拒绝(maxConsecutive: 3)或总计拒绝过多(maxTotal: 20)时,系统会回退到人工审批模式(denialTracking.ts 第 8~33 行)。这防止了分类器"卡死"导致用户完全无法进行任何操作。

5.4 分类器审批追踪

src/utils/classifierApprovals.ts 维护了一个全局的 CLASSIFIER_APPROVALS Map,用于追踪哪些工具调用是被分类器自动批准的:

type ClassifierApproval = {
  classifier: 'bash' | 'auto-mode'
  matchedRule?: string
  reason?: string
}

const CLASSIFIER_APPROVALS = new Map<string, ClassifierApproval>()

这在 UI 层(UserToolSuccessMessage.tsx)用于向用户展示 "已由 auto-mode 分类器自动批准" 的提示,增强系统透明度。

六、关键安全设计原则

纵观整个权限系统,Claude Code 遵循了几条核心的安全设计原则:

  1. Deny-by-Default:没有明确授权的操作默认需要审批
  2. Defense in Depth:多层检查(规则 → 工具 → 模式 → 分类器),任何一层都可以拦截危险操作
  3. Immutable Safety Checks:敏感路径检查是 bypass 免疫的,作为最后防线
  4. Transparency:通过 PermissionRuleExplanationPermissionExplanation 向用户清晰解释每一次审批的原因
  5. Graceful Degradation:分类器不可用时可以 fail-open 或 fail-closed(通过 tengu_iron_gate_closed Feature Gate 控制),并且超过拒绝阈值后自动回退到人工审批
  6. Least Privilegeauto 模式会自动剥离危险的宽泛权限规则,确保自动化运行在最小权限原则下

七、总结

Claude Code 的权限与审批系统是一个工程复杂度极高的安全子系统。它需要在"让 AI 自由发挥以提升效率"和"确保用户系统安全不被破坏"之间走钢丝。

src/types/permissions.ts 中纯类型定义的设计(打破循环依赖),到 src/utils/permissions/ 中 24 个模块的精密协作,再到 src/components/permissions/ 中 40 多个 UI 组件的人性化交互,每一层都体现了对安全与体验的深度思考。

最核心的 hasPermissionsToUseTool 函数虽然长达数百行,但其检查顺序经过精心编排:Deny 规则 → Ask 规则 → 工具自定义检查 → Safety Check → bypass/allow 快速通道 → 模式转换 → 分类器。这个顺序确保了拒绝总是优先于允许安全总是优先于便利

对于正在构建 AI Agent 的开发者来说,Claude Code 的权限系统提供了一个优秀的参考范式:任何能够执行代码、访问文件、连接网络的 Agent,都必须拥有同等严格甚至更严格的权限控制机制。权限不是可选功能,而是 Agent 安全架构的基石。