6.9 KiB
title, description, keywords
| title | description | keywords | ||||||
|---|---|---|---|---|---|---|---|---|
| 权限模型 - Allow/Ask/Deny 三级权限体系 | 详解 Claude Code 的三级权限模型实现:基于 src/utils/permissions/permissions.ts 的规则匹配引擎、五层规则来源优先级、工具名/命令/路径三维度匹配、Denial Tracking 死循环防护、权限模式切换机制。 |
|
{/* 本章目标:基于源码揭示权限系统的完整实现 */}
三种权限行为
每一次工具调用,系统都会做出三种裁决之一:
| 行为 | 含义 | 返回类型 | 典型场景 |
|---|---|---|---|
| Allow | 自动放行,用户无感知 | { behavior: 'allow', updatedInput, decisionReason } |
Read 读取项目内文件 |
| Ask | 弹出确认对话框 | { behavior: 'ask', message, suggestions, metadata } |
Bash 执行未知命令 |
| Deny | 直接拒绝 | { behavior: 'deny', message, decisionReason } |
尝试执行被禁止的命令 |
这些行为由 PermissionResult 类型定义(src/utils/permissions/PermissionResult.ts)。
权限规则的来源
规则从 8 个来源汇聚(PERMISSION_RULE_SOURCES,permissions.ts:109),优先级从低到高(后者覆盖前者):
1. userSettings — ~/.claude/settings.json(跨项目)
2. projectSettings — .claude/settings.json(团队共享)
3. localSettings — .claude/settings.local.json(gitignored,个人覆盖)
4. flagSettings — --settings 命令行参数
5. policySettings — 企业管理员下发的策略(用户不可覆盖)
6. cliArg — 命令行 --allow/--deny 参数
7. command — Skill 工具的 allowedTools 白名单
8. session — 用户在当前对话中手动授权("Always allow")
每个来源维护三个数组:alwaysAllowRules[source]、alwaysAskRules[source]、alwaysDenyRules[source]。
规则数据结构为 PermissionRule:
{
source: PermissionRuleSource // 来自哪个层级
ruleBehavior: 'allow' | 'ask' | 'deny'
ruleValue: {
toolName: string // 如 "Bash"、"mcp__server1"
ruleContent?: string // 如 "git *"、"src/**"
}
}
规则匹配引擎
三维度匹配
permissions.ts 实现了三种匹配维度:
1. 工具名匹配(toolMatchesRule(),第 238 行)
匹配整个工具,仅当规则没有 ruleContent:
// 精确匹配
rule "Bash" → 匹配 BashTool
rule "mcp__server1" → 匹配该 MCP Server 的所有工具(server 级别)
rule "mcp__server1__*" → 通配符匹配(同上)
MCP 工具使用 getToolNameForPermissionCheck() 获取匹配名称,支持有前缀(mcp__server__tool)和无前缀模式。
2. 命令模式匹配(BashTool 的 checkPermissions())
BashTool 通过 preparePermissionMatcher()(Tool.ts:520)解析命令模式:
{"tool": "Bash", "ruleContent": "git *"} → 匹配 "git commit -m 'fix'"
命令通过 AST 解析(readOnlyValidation.ts 使用 tree-sitter bash),提取第一个子命令进行匹配。
3. 路径匹配(文件工具的 checkPermissions())
Read/Edit/Write 工具通过 getPath() 提取文件路径,与 ruleContent 中的 glob 模式匹配:
{"tool": "Edit", "ruleContent": "src/**"} → 匹配 "src/utils/foo.ts"
权限检查的完整流程
每次工具调用的权限检查(canUseTool() → checkPermissions())经过以下步骤:
1a. Blanket deny 检查
getDenyRuleForTool() → 工具名完全匹配 deny 规则?
↓ 命中 → deny(工具在 getTools() 阶段就被过滤掉)
1b. Blanket allow 检查
toolAlwaysAllowedRule() → 工具名完全匹配 allow 规则?
↓ 命中 → allow
2. 工具自身 checkPermissions()
各工具有自定义逻辑:
- BashTool: readOnlyValidation → sandbox 判定 → AST 解析 → 模式匹配
- FileEditTool: 路径白名单检查
- SkillTool: safe properties 白名单 + 精确/前缀匹配
↓ 返回 PermissionResult
3. Hook 系统
executePermissionRequestHooks() → PreToolUse hook 可以 override
↓ hook 返回 deny → deny
↓ hook 返回 ask → 升级为 ask
4. Ask 规则检查
getAskRules() → 命中 → ask
5. 默认行为
根据当前 permissionMode 决定默认行为
- 'default': 大部分工具 ask
- 'plan': 写操作 deny,读操作 allow
- 'bypass': 全部 allow
权限模式
| 模式 | PermissionMode 值 |
适用场景 | 行为 |
|---|---|---|---|
| Default | 'default' |
日常使用 | 敏感操作逐一确认 |
| Plan Mode | 'plan' |
探索阶段 | 只能读不能写(isReadOnly() 检查) |
| Accept Edits | 'acceptEdits' |
快速迭代 | 工作区内文件编辑自动放行,其他操作仍需确认 |
| Don't Ask | 'dontAsk' |
减少打断 | 尽量自动决策,减少确认弹窗 |
| Auto | 'auto' |
信任 AI | 通过 transcript classifier 自动决策(需 TRANSCRIPT_CLASSIFIER feature flag) |
| Bypass | 'bypassPermissions' |
完全信任 | 所有操作自动放行(需显式 --dangerously-skip-permissions) |
Plan Mode 切换由 EnterPlanModeTool.call() 触发:
// EnterPlanModeTool.ts:88
context.setAppState(prev => ({
...prev,
toolPermissionContext: applyPermissionUpdate(
prepareContextForPlanMode(prev.toolPermissionContext),
{ type: 'setMode', mode: 'plan', destination: 'session' },
),
}))
退出时由 ExitPlanModeV2Tool 恢复为之前的模式。
Denial Tracking:死循环防护
src/utils/permissions/denialTracking.ts 实现了拒绝追踪机制:
const DENIAL_LIMITS = {
maxConsecutive: 3, // 同一工具连续拒绝上限
maxTotal: 20, // 总拒绝上限
}
当 AI 被连续拒绝同一类操作达到上限时:
recordDenial()记录拒绝,增加计数shouldFallbackToPrompting()检测到连续拒绝,返回 true- 系统向 AI 注入消息:"Your previous tool call was rejected..."
- AI 被迫改变策略,避免"反复请求同一个被拒操作"的死循环
操作成功时调用 recordSuccess() 重置计数。
规则的运行时更新
权限规则可以在运行时动态更新(applyPermissionUpdate(),PermissionUpdate.ts):
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 }
当用户在 Ask 对话框中选择 "Always allow",系统调用 persistPermissionUpdates() 将规则写入对应层级的 settings 文件(project/user/managed),同时更新内存中的 toolPermissionContext。