模块五:权限体系 | 前置依赖:第 10 课 | 预计学习时间:70 分钟
学习目标
完成本课后,你将能够:
- 列举 Claude Code 的权限模式并解释每种模式的行为差异
- 说明三级风险评估(LOW/MEDIUM/HIGH)如何影响权限判定
- 描述权限规则的来源层级与合并策略
- 理解 PermissionContext 在不同运行环境中的 handler 分发机制
- 追踪一次工具调用从
useCanUseTool到最终判定的完整链路
15.1 权限模式(Permission Modes)
Claude Code 的权限系统围绕"模式"构建。模式决定了工具调用时系统的默认行为。
外部模式(用户可见)
// types/permissions.ts
export const EXTERNAL_PERMISSION_MODES = [
'acceptEdits', // 自动接受文件编辑,其余仍需确认
'bypassPermissions', // 跳过所有权限检查(危险!)
'default', // 默认模式 — 每次调用都询问用户
'dontAsk', // 不询问,直接拒绝需要权限的操作
'plan', // 规划模式 — 只允许读取操作
] as const
内部模式(含运行时专用)
export type InternalPermissionMode =
| ExternalPermissionMode
| 'auto' // ML 分类器自动判定(feature-gated)
| 'bubble' // 权限请求"冒泡"到上层(Swarm worker 用)
auto 模式通过 feature gate TRANSCRIPT_CLASSIFIER 控制,只有在构建时启用该特性后才会出现在运行时验证集中:
export const INTERNAL_PERMISSION_MODES = [
...EXTERNAL_PERMISSION_MODES,
...(feature('TRANSCRIPT_CLASSIFIER')
? (['auto'] as const)
: ([] as const)),
] as const
模式行为对照表
┌────────────────────┬──────────┬──────────┬──────────┐
│ 模式 │ 读操作 │ 写操作 │ 危险操作 │
├────────────────────┼──────────┼──────────┼──────────┤
│ default │ allow │ ask │ ask │
│ acceptEdits │ allow │ allow* │ ask │
│ plan │ allow │ deny │ deny │
│ dontAsk │ allow │ deny │ deny │
│ bypassPermissions │ allow │ allow │ allow │
│ auto │ allow │ 分类器 │ 分类器 │
└────────────────────┴──────────┴──────────┴──────────┘
* acceptEdits 对文件编辑自动允许,对 Bash 等仍需确认
15.2 三级风险评估
每个权限判定都可附带风险等级,用于向用户展示操作的危险程度:
// types/permissions.ts
export type RiskLevel = 'LOW' | 'MEDIUM' | 'HIGH'
export type PermissionExplanation = {
riskLevel: RiskLevel
explanation: string // 给用户看的一句话解释
reasoning: string // 详细推理过程
risk: string // 风险描述
}
风险等级语义
LOW ─── 读取操作、安全的文件操作
例:cat README.md, ls src/
MEDIUM ─── 写操作、可逆的修改
例:编辑源码文件、创建新文件
HIGH ─── 不可逆操作、系统级影响
例:rm -rf, git push --force, 修改 .bashrc
风险等级通过 Permission Explainer(LLM 调用)动态计算,这部分我们将在第 16 课详细讨论。
15.3 权限行为三态
权限判定的结果是三选一的 PermissionBehavior:
export type PermissionBehavior = 'allow' | 'deny' | 'ask'
加上内部使用的 passthrough,完整决策类型为:
export type PermissionResult<Input> =
| PermissionDecision<Input> // allow | ask | deny
| {
behavior: 'passthrough' // "我不管,交给下一个检查"
message: string
suggestions?: PermissionUpdate[]
pendingClassifierCheck?: PendingClassifierCheck
}
决策数据结构
PermissionAllowDecision
├── behavior: 'allow'
├── updatedInput?: Input ← 可修改工具输入
├── userModified?: boolean ← 用户是否编辑了输入
├── decisionReason? ← 为什么允许
├── acceptFeedback?: string ← 用户附加的反馈
└── contentBlocks? ← 附带的内容块(如图片)
PermissionAskDecision
├── behavior: 'ask'
├── message: string ← 显示给用户的问题
├── suggestions?: PermissionUpdate[] ← 可选的一键操作
├── pendingClassifierCheck? ← 后台分类器检查
└── isBashSecurityCheckForMisparsing? ← 安全检查标记
PermissionDenyDecision
├── behavior: 'deny'
├── message: string ← 拒绝原因
└── decisionReason ← 必须提供原因
15.4 权限规则系统
规则来源(PermissionRuleSource)
权限规则从七个来源汇聚,优先级从高到低:
export type PermissionRuleSource =
| 'policySettings' // 组织策略(最高优先级)
| 'flagSettings' // CLI 参数
| 'cliArg' // 命令行 --allow / --deny
| 'localSettings' // .claude/settings.local.json
| 'projectSettings' // .claude/settings.json
| 'userSettings' // ~/.claude/settings.json
| 'command' // 命令内置规则
| 'session' // 当前会话临时规则
规则结构
export type PermissionRuleValue = {
toolName: string // 例: "Bash", "FileEdit", "mcp__server1"
ruleContent?: string // 例: "prefix:git *", "*.ts" — 可选的内容匹配
}
export type PermissionRule = {
source: PermissionRuleSource
ruleBehavior: PermissionBehavior // allow | deny | ask
ruleValue: PermissionRuleValue
}
规则匹配示例
规则 "Bash" → 匹配所有 Bash 工具调用
规则 "Bash(prefix:git *)" → 匹配以 "git " 开头的 Bash 命令
规则 "FileEdit(*.ts)" → 匹配编辑 .ts 文件
规则 "mcp__server1" → 匹配该 MCP server 的所有工具
规则 "mcp__server1__*" → 同上(通配符形式)
ToolPermissionContext — 运行时权限上下文
所有规则最终汇聚到 ToolPermissionContext,存储在 AppState 中:
export type ToolPermissionContext = {
readonly mode: PermissionMode
readonly additionalWorkingDirectories: ReadonlyMap<string, ...>
readonly alwaysAllowRules: ToolPermissionRulesBySource
readonly alwaysDenyRules: ToolPermissionRulesBySource
readonly alwaysAskRules: ToolPermissionRulesBySource
readonly isBypassPermissionsModeAvailable: boolean
readonly strippedDangerousRules?: ToolPermissionRulesBySource
readonly shouldAvoidPermissionPrompts?: boolean
readonly awaitAutomatedChecksBeforeDialog?: boolean
readonly prePlanMode?: PermissionMode
}
规则按行为分三桶存储(allow / deny / ask),每桶再按来源分层:
alwaysAllowRules = {
userSettings: ["Bash(prefix:git *)"],
projectSettings: ["FileRead"],
session: ["Bash(prefix:npm test)"]
}
alwaysDenyRules = {
policySettings: ["Bash(prefix:curl *)"],
userSettings: ["WebFetch"]
}
15.5 受保护文件与目录
Claude Code 维护一份敏感文件/目录清单,对这些路径的写操作需要额外审批:
// utils/permissions/filesystem.ts
export const DANGEROUS_FILES = [
'.gitconfig', // Git 配置 — 可执行任意命令
'.gitmodules', // Git 子模块 — 可指向恶意仓库
'.bashrc', // Shell 启动脚本 — 代码注入
'.bash_profile',
'.zshrc',
'.zprofile',
'.profile',
'.ripgreprc', // ripgrep 配置
'.mcp.json', // MCP 服务器配置
'.claude.json', // Claude Code 配置
] as const
export const DANGEROUS_DIRECTORIES = [
'.git', // Git 内部结构
'.vscode', // VS Code 配置
'.idea', // JetBrains 配置
'.claude', // Claude Code 配置目录
] as const
大小写标准化防护
macOS/Windows 的文件系统不区分大小写,攻击者可以用 .cLauDe/Settings.locaL.json 绕过检查:
export function normalizeCaseForComparison(path: string): string {
return path.toLowerCase() // 始终转为小写比较
}
15.6 useCanUseTool — 权限判定入口
hooks/useCanUseTool.tsx 是所有工具调用的权限入口。它是一个 React Hook,返回一个异步函数:
export type CanUseToolFn<Input> = (
tool: ToolType,
input: Input,
toolUseContext: ToolUseContext,
assistantMessage: AssistantMessage,
toolUseID: string,
forceDecision?: PermissionDecision<Input>
) => Promise<PermissionDecision<Input>>
完整判定流程
useCanUseTool(tool, input, context, message, toolUseID)
│
├─ 1. 检查中止信号(abortController.signal)
│ └─ 已中止? → cancelAndAbort()
│
├─ 2. hasPermissionsToUseTool(tool, input, context)
│ │ ← 核心权限检查(见下方)
│ │
│ ├─ result.behavior === 'allow'
│ │ ├─ 记录分类器审批(如有)
│ │ ├─ logDecision('accept', 'config')
│ │ └─ resolve(buildAllow(...))
│ │
│ ├─ result.behavior === 'deny'
│ │ ├─ logDecision('reject', 'config')
│ │ ├─ auto 模式: 记录拒绝 + 通知 UI
│ │ └─ resolve(result)
│ │
│ └─ result.behavior === 'ask'
│ │
│ ├─ 3. awaitAutomatedChecksBeforeDialog?
│ │ └─ handleCoordinatorPermission()
│ │ ├─ 运行 hooks
│ │ ├─ 尝试分类器
│ │ └─ 都不行 → 继续
│ │
│ ├─ 4. 是 Swarm worker?
│ │ └─ handleSwarmWorkerPermission()
│ │ ├─ 尝试分类器
│ │ └─ 转发到 leader
│ │
│ ├─ 5. 投机分类器检查(2秒竞速)
│ │ └─ peekSpeculativeClassifierCheck()
│ │ ├─ 高置信度匹配 → 自动批准
│ │ └─ 超时/不匹配 → 继续
│ │
│ └─ 6. handleInteractivePermission()
│ └─ 向用户展示权限弹窗
│
└─ catch → 错误处理 → cancelAndAbort()
hasPermissionsToUseTool 内部逻辑
这个函数在 utils/permissions/permissions.ts 中实现,是真正的"规则引擎":
hasPermissionsToUseTool(tool, input, context)
│
├─ hasPermissionsToUseToolInner()
│ ├─ 检查 deny 规则 → 命中 → deny
│ ├─ 检查 allow 规则 → 命中 → allow
│ ├─ 检查 ask 规则 → 命中 → ask
│ ├─ 检查模式默认行为
│ └─ 工具自身 checkPermissions()
│
├─ allow? → 重置连续拒绝计数
│
├─ ask + dontAsk 模式? → 转为 deny
│
└─ ask + auto 模式? → 交给 ML 分类器
├─ 安全检查不可自动批准? → 仍然 ask/deny
├─ 检查拒绝追踪阈值
└─ 调用 classifyYoloAction()
15.7 PermissionContext — 权限操作上下文
hooks/toolPermission/PermissionContext.ts 创建一个冻结的上下文对象,封装权限判定过程中需要的所有操作:
function createPermissionContext(
tool, input, toolUseContext, assistantMessage,
toolUseID, setToolPermissionContext, queueOps?
) {
const ctx = {
tool, input, toolUseContext, assistantMessage,
messageId, toolUseID,
// 日志
logDecision(args, opts),
logCancelled(),
// 权限持久化
async persistPermissions(updates): Promise<boolean>,
// 中止处理
resolveIfAborted(resolve): boolean,
cancelAndAbort(feedback?, isAbort?, contentBlocks?),
// 构建决策
buildAllow(updatedInput, opts?): PermissionAllowDecision,
buildDeny(message, reason): PermissionDenyDecision,
// 用户/Hook 批准处理
async handleUserAllow(input, updates, feedback?, ...),
async handleHookAllow(input, updates, ...),
// 分类器(feature-gated)
async tryClassifier(pending, updatedInput),
// Hook 执行
async runHooks(mode, suggestions, input?, ...),
// 确认队列操作
pushToQueue(item),
removeFromQueue(),
updateQueueItem(patch),
}
return Object.freeze(ctx) // 不可变
}
三种 Handler
根据运行环境,权限请求走不同的 handler:
┌─────────────────────┬────────────────────────────────────┐
│ Handler │ 使用场景 │
├─────────────────────┼────────────────────────────────────┤
│ interactiveHandler │ 主 Agent(有终端交互的场景) │
│ │ 展示权限弹窗给用户 │
│ │ 同时异步运行 hooks + 分类器 │
│ │ 用户交互与自动检查竞速 │
├─────────────────────┼────────────────────────────────────┤
│ coordinatorHandler │ Coordinator worker │
│ │ 先顺序执行 hooks → 分类器 │
│ │ 都不行才 fallthrough 到交互弹窗 │
├─────────────────────┼────────────────────────────────────┤
│ swarmWorkerHandler │ Swarm worker(子进程) │
│ │ 尝试分类器自动批准 │
│ │ 不行就转发到 leader 处理 │
└─────────────────────┴────────────────────────────────────┘
interactiveHandler 的竞速模型
┌─── hooks 执行 ──────── 结果? ─── resolve
│
用户看到弹窗 ──────┼─── 分类器异步执行 ──── 结果? ─── resolve
│
└─── 用户点击 ─────────── 选择? ─── resolve
↕ 三者竞速,第一个完成的 resolve 生效
↕ ResolveOnce 保证只 resolve 一次
createResolveOnce 是关键的竞态安全组件:
function createResolveOnce<T>(resolve: (value: T) => void): ResolveOnce<T> {
let claimed = false
let delivered = false
return {
resolve(value: T) {
if (delivered) return
delivered = true; claimed = true
resolve(value)
},
isResolved() { return claimed },
claim() { // 原子性检查+标记
if (claimed) return false
claimed = true
return true
},
}
}
15.8 权限决策原因(DecisionReason)
每个权限决策都附带"为什么"的解释:
export 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: 'permissionPromptTool'; ... }
| { type: 'sandboxOverride'; reason: 'excludedCommand' | 'dangerouslyDisableSandbox' }
| { type: 'workingDir'; reason: string }
| { type: 'safetyCheck'; reason: string; classifierApprovable: boolean }
| { type: 'asyncAgent'; reason: string }
| { type: 'other'; reason: string }
safetyCheck 类型特别值得注意:
{
type: 'safetyCheck',
reason: string,
classifierApprovable: boolean // true → auto 模式可让分类器判定
// false → 必须人工确认
}
classifierApprovable: true 用于敏感文件路径(.claude/, .git/, shell 配置),分类器有上下文可以做出合理判断。classifierApprovable: false 用于 Windows 路径绕过尝试和跨机器 bridge 消息,必须由人工确认。
15.9 权限更新与持久化
用户在权限弹窗中做出的选择可以持久化为规则:
export type PermissionUpdate =
| { type: 'addRules'; destination; rules; behavior }
| { type: 'replaceRules'; destination; rules; behavior }
| { type: 'removeRules'; destination; rules; behavior }
| { type: 'setMode'; destination; mode }
| { type: 'addDirectories'; destination; directories }
| { type: 'removeDirectories'; destination; directories }
持久化目标:
export type PermissionUpdateDestination =
| 'userSettings' // ~/.claude/settings.json
| 'projectSettings' // .claude/settings.json
| 'localSettings' // .claude/settings.local.json
| 'session' // 仅当前会话(不写磁盘)
| 'cliArg' // 命令行参数级别
15.10 端到端示例:用户执行 npm test
用户输入: "运行测试"
│
▼
模型生成: tool_use(Bash, {command: "npm test"})
│
▼
useCanUseTool(BashTool, {command: "npm test"}, ...)
│
├─ hasPermissionsToUseTool()
│ ├─ 检查 deny 规则 → 无匹配
│ ├─ 检查 allow 规则 → 假设有 "Bash(prefix:npm test)"
│ └─ 匹配! → return { behavior: 'allow', decisionReason: { type: 'rule', ... } }
│
├─ behavior === 'allow'
├─ logDecision('accept', 'config')
└─ resolve(buildAllow({command: "npm test"}))
→ 工具直接执行,用户无感知
如果没有匹配的 allow 规则且模式是 default:
├─ hasPermissionsToUseTool()
│ ├─ 检查 deny 规则 → 无匹配
│ ├─ 检查 allow 规则 → 无匹配
│ └─ 默认模式 → return { behavior: 'ask', message: "..." }
│
├─ behavior === 'ask'
├─ handleInteractivePermission()
│ ├─ 显示弹窗: "Allow Bash: npm test? [y/n/always]"
│ ├─ 用户选 "always"
│ ├─ persistPermissions([{type:'addRules', ...}])
│ └─ resolve(buildAllow(...))
│
→ 规则保存到 settings.json,下次自动允许
课后练习
练习 1:模式推演
分析以下场景在不同模式下的行为:
default模式下执行rm -rf node_modulesacceptEdits模式下编辑.bashrcplan模式下运行cat package.jsonauto模式下执行git push origin main
练习 2:规则优先级
给定以下规则配置,判断 Bash(prefix:curl https://api.example.com) 的权限结果:
{
"policySettings": { "deny": ["Bash(prefix:curl *)"] },
"userSettings": { "allow": ["Bash(prefix:curl https://api.example.com)"] },
"session": { "allow": ["Bash"] }
}
练习 3:Handler 选择
阅读 useCanUseTool.tsx 源码,画出以下场景各自走哪个 handler 路径:
- 普通 CLI 终端中执行工具
- SDK headless 模式(
shouldAvoidPermissionPrompts: true) - Swarm worker 子进程中执行工具
练习 4:ResolveOnce 竞态
实现一个简化版的 ResolveOnce<T>,测试以下场景:
- 分类器和用户同时返回结果
- 用户中止(abort)后分类器返回结果
- Hook 拒绝后用户点击允许
本课小结
| 要点 | 内容 |
|---|---|
| 权限模式 | 7 种模式:default/acceptEdits/plan/dontAsk/bypassPermissions/auto/bubble |
| 行为三态 | allow(允许)、deny(拒绝)、ask(询问用户) |
| 风险等级 | LOW/MEDIUM/HIGH,通过 Permission Explainer 动态评估 |
| 规则来源 | 7 层来源,policy 最高优先级,session 最低 |
| 受保护文件 | .gitconfig/.bashrc/.claude 等敏感配置 |
| Handler 分发 | interactive(主 Agent)、coordinator(协调器)、swarmWorker(子进程) |
| 竞速安全 | ResolveOnce + claim() 保证并发场景下只 resolve 一次 |
下一课预告
第 16 课:高级权限 — 自动模式与安全防护 — 深入 auto 模式的 ML 分类器实现、YOLO 分类器的两阶段判定、Bash 命令的路径遍历防护、以及拒绝追踪的熔断机制。