范围:
utils/permissions/(24 个文件, ~320KB),utils/settings/(17 个文件, ~135KB),utils/sandbox/(2 个文件, ~37KB)一句话概括: Claude Code 如何决定一个工具调用的生死 —— 经过规则、分类器、钩子和操作系统沙箱的七步考验。
核心函数 hasPermissionsToUseToolInner() 实现了一个严格有序的权限评估流水线。每一步都可以短路整个链条:
硬拒绝 —— 无法覆盖。规则来源包括:userSettings、projectSettings、localSettings、policySettings、flagSettings、cliArg、command、session。
关键设计:当沙箱启用并配置了自动允许时,沙箱化的 Bash 命令可以跳过询问规则。非沙箱化命令(被排除的命令、dangerouslyDisableSandbox)仍然遵守规则。
每个工具实现自己的 checkPermissions()。BashTool 检查子命令,EditTool 检查文件路径,WebFetch 验证域名。
| 步骤 | 检查 | 能否被绕过? |
|---|---|---|
| 1d | 工具实现拒绝 | ❌ 不能 |
| 1e | requiresUserInteraction() 返回 true |
❌ 不能 |
| 1f | 内容级询问规则(如 Bash(npm publish:*)) |
❌ 不能 |
| 1g | 安全检查(.git/、.claude/、.vscode/、shell 配置) |
❌ 不能 |
这四项检查是绕过免疫的 —— 即使在 bypassPermissions 模式下也会触发。
如果当前处于 bypassPermissions 模式(或 plan 模式但原始模式是 bypass),直接允许。
支持 MCP 服务器级匹配:规则 mcp__server1 匹配 mcp__server1__tool1。
如果没有任何决策,默认询问用户。
| 模式 | ask 变为 |
安全检查 | 说明 |
|---|---|---|---|
default |
提示用户 | 提示 | 标准交互模式 |
plan |
提示用户 | 提示 | 暂存前置模式以便恢复 |
acceptEdits |
允许(仅文件编辑) | 提示 | 非编辑工具仍需提示 |
bypassPermissions |
全部允许 | 仍然提示 | 可被 GrowthBook 门控或设置禁用 |
dontAsk |
拒绝 | 提示 | 静默拒绝,模型看到拒绝消息 |
auto |
分类器决策 | 提示 | 两阶段 XML 分类器,需门控 |
transitionPermissionMode() 集中处理所有副作用:
- 进入 auto 模式:剥离危险权限(
Bash(*)、python:*、Agent 允许列表)—— 这些权限会绕过分类器 - 离开 auto 模式:恢复被剥离的权限
- 进入 plan 模式:保存前置模式以便恢复
- 离开 plan 模式:恢复之前的模式
规则从 7 个来源加载:
| 来源 | 文件 | 范围 |
|---|---|---|
userSettings |
~/.claude/settings.json |
用户全局 |
projectSettings |
.claude/settings.json |
项目级,提交到仓库 |
localSettings |
.claude/settings.local.json |
项目级,gitignore |
policySettings |
企业 MDM 策略 | 组织范围 |
flagSettings |
GrowthBook 功能标志 | 远程配置 |
cliArg |
--allowed-tools、--disallowed-tools |
单次调用 |
session |
运行时通过权限对话框添加 | 单次会话 |
ToolName → 匹配整个工具
ToolName(content) → 匹配带特定内容的工具
Bash(npm test:*) → Bash 命令的前缀匹配
mcp__server1 → 匹配 MCP 服务器的所有工具
Agent(Explore) → 匹配特定代理类型
当模式为 auto 时,ask 决策经过三级快速路径系统后才到达 AI 分类器:
以 acceptEdits 模式重新运行 tool.checkPermissions()。如果允许,跳过分类器。例外:AgentTool 和 REPLTool 被排除 —— REPL 代码可能包含 VM 逃逸。
列入允许名单的工具完全跳过分类器。
分类器使用单独的 LLM API 调用(sideQuery)和专用系统提示词:
阶段 1(快速):max_tokens=64,引导立即给出 <block>yes/no。如果允许 → 结束。
阶段 2(思考):仅在阶段 1 阻止时运行。使用思维链减少误报。指令说明:"审查分类过程... 覆盖阻止需要显式用户确认。"
分类器看到的是紧凑版记录 —— 不是完整对话:
{"user":"修复登录bug"}
{"Bash":"ls src/"}
{"Read":"src/auth.ts"}
{"Bash":"npm test"} ← 正在被分类的操作关键安全设计:助手文本被排除 —— 仅包含 tool_use 区块。这防止模型构造影响分类器决策的诱导性文本。
进入 auto 模式时,会绕过分类器的权限被剥离:
Bash(无内容)→ 允许所有命令Bash(python:*)、Bash(node:*)→ 允许任意代码执行PowerShell(iex:*)、PowerShell(Start-Process:*)→ 代码执行Agent(任何允许规则)→ 绕过子代理评估
被剥离的规则暂存到 strippedDangerousRules,离开 auto 模式时恢复。
// 源码位置: src/utils/permissions/denialTracking.ts:5-10
export const DENIAL_LIMITS = {
maxConsecutive: 3, // 连续 3 次阻止 → 回退到用户提示
maxTotal: 20, // 单次会话总计 20 次阻止 → 回退
}超过限制时:
- 交互模式:回退到用户提示
- 无头模式:抛出
AbortError—— 会话终止
| 场景 | iron_gate = true(默认) | iron_gate = false |
|---|---|---|
| API 错误 | 拒绝(失败关闭) | 回退到用户提示(失败开放) |
| 记录过长 | 无头时中止;交互时提示 | 相同 |
| 无法解析响应 | 视为阻止 | 相同 |
tengu_iron_gate_closed 功能标志控制失败时关闭 vs. 开放行为,每 30 分钟刷新。
后台/异步代理无法显示权限提示。流水线的处理方式:
- 运行
PermissionRequest钩子 —— 给钩子机会做出决策 - 如果没有钩子决策 → 自动拒绝
钩子可以 allow(带可选输入修改)、deny 或 interrupt(中止整个代理)。
沙箱提供内核级强制执行,补充应用层权限流水线:
当 autoAllowBashIfSandboxed 启用时:
- 通过
shouldUseSandbox()检查的 Bash 命令 → 自动允许(跳过 ask 规则) - OS 沙箱强制执行文件系统和网络限制
- 应用层检查对沙箱化操作变得冗余
| 保护 | 实现 |
|---|---|
| 文件系统写入 | denyWrite 列表(设置文件、.claude/skills) |
| 文件系统读取 | denyRead 列表(敏感路径) |
| 网络访问 | 来自 WebFetch 规则的域名允许列表 |
| 裸 Git 仓库攻击 | 命令前后文件清扫 |
| 符号链接追踪 | O_NOFOLLOW 文件操作 |
| 设置逃逸 | 无条件拒绝写入 settings.json |
以下模式可直接应用于其他 AI 智能体权限系统或安全流水线。
场景: 不同规则源需要不同的覆盖语义。
实践: 将权限评估结构化为严格有序的流水线,其中某些步骤对所有绕过模式免疫。
Claude Code 中的应用: 步骤 1d-1g 即使在 bypassPermissions 模式下也会触发。
场景: AI 安全分类器可能被模型自己的说服性输出影响。 实践: 从分类器输入中剥除助手文本,仅包含结构化 tool_use 区块。 Claude Code 中的应用: YOLO 分类器的记录排除助手文本,防止社会工程攻击。
场景: 进入高自动化模式不应永久破坏手动权限规则。
实践: 进入模式时暂存被剥离的规则,退出时恢复。
Claude Code 中的应用: 危险的 Bash(*) 规则在进入 auto 模式时暂存,退出时恢复。
场景: 被阻止的操作导致无限重试循环。 实践: 追踪连续和总计拒绝次数,超限后触发回退。 Claude Code 中的应用: 连续 3 次或总计 20 次拒绝触发模式回退。
源码坐标: src/services/oauth/client.ts、src/auth/
权限系统的权威性始于身份验证。Claude Code 支持两条认证路径:
| 路径 | 方法 | Token 类型 |
|---|---|---|
| Console API Key | ANTHROPIC_API_KEY 环境变量或配置 |
静态密钥,无过期管理 |
| Claude.ai OAuth | PKCE 授权码流程 | JWT + 刷新,~1h 过期 |
关键安全属性:
- PKCE (S256):防止授权码拦截 ——
code_verifier在客户端生成,直到交换时才发送 - Token 生命周期:Access token ~1h 过期;Refresh token 存储在 SecureStorage 中,自动在过期前刷新
- 撤销处理:过期/已撤销的 token 触发重新认证,而非静默失败
Bridge 会话使用 generation counter(代数计数器)防止过期刷新竞态,在过期前 5 分钟调度刷新,失败最多重试 3 次。
源码坐标: src/utils/settings/settings.ts、src/utils/settings/constants.ts
权限规则从五层设置系统加载(详见第 16 篇),但权限相关的特殊方面值得在此说明。
企业策略设置(policySettings)具有特殊属性:它们无法被低优先级来源覆盖。当策略拒绝某工具时,任何项目或用户设置都无法重新允许它。
企业部署可以完全禁用绕过模式:
permissions: {
disableBypassPermissionsMode: 'disable', // 完全移除该选项
deny: [
{ tool: 'Bash', content: 'rm -rf:*' }, // 策略级拒绝
]
}源码坐标: src/utils/secureStorage/
macOS 通过 security CLI 使用原生 Keychain,其他平台优雅降级到明文存储。
凭证存储韧性的最重要模式:当 security 子进程失败时,继续使用缓存的成功数据而非返回 null。如果没有这一策略,macOS Keychain Service 的临时重启会导致所有进行中的请求以"未登录"失败。
macOS security 命令有一个未文档化的 stdin 限制(4096 字节)。超过此限制会导致静默数据截断 → 凭证损坏。这迫使设计选择最小化存储的凭证大小。
| 组件 | 行数 | 角色 |
|---|---|---|
permissions.ts |
1,487 | 核心流水线:7步评估、模式变换 |
permissionSetup.ts |
1,533 | 模式初始化、危险权限检测 |
yoloClassifier.ts |
1,496 | auto 模式的两阶段 XML 分类器 |
PermissionMode.ts |
142 | 6种权限模式 + 配置 |
PermissionRule.ts |
41 | 规则类型:{toolName, ruleContent?} |
denialTracking.ts |
46 | 熔断器:连续 3 次 / 总计 20 次 |
permissionRuleParser.ts |
~200 | 规则字符串 ↔ 结构化值转换 |
permissionsLoader.ts |
~250 | 从 7 个设置来源加载规则 |
shadowedRuleDetection.ts |
~250 | 检测冲突/遮蔽的规则 |
sandbox-adapter.ts |
986 | OS 沙箱:seatbelt / bubblewrap |
权限流水线是 Claude Code 架构最精密的子系统。其七步评估顺序 —— 包含四项绕过免疫的安全检查 —— 代表了关于 AI 代理执行任意代码时可能发生什么的血泪教训。YOLO 分类器的引入展示了系统从纯规则匹配向 AI 辅助安全决策的演进,同时保留确定性护栏作为安全网。
上一篇: ← 06 — Bash 执行引擎