安全与权限系统深度分析
本文基于 Codev (Claude Code) 源代码,深入分析其安全架构与权限子系统。 版本参考:commit
835ff5a/cdb3bdd
1. 设计哲学
1.1 Deny-First 原则
系统中所有权限检查的默认行为是拒绝。未在规则中明确允许的操作,最终都会向用户发起询问或直接被拒绝。这一原则贯穿整个权限管道(Authorization Pipeline),体现在:
PermissionResult的默认行为是ask(询问用户),而非allow- 工具实现的
checkPermissions()方法若返回passthrough,上层会将其转换为ask(src/utils/permissions/permissions.ts:1300-1310) bypassPermissions模式是唯一能跳过所有检查的模式,但该模式可以通过 Statsig 门控(tengu_disable_bypass_permissions_mode)被完全禁用
1.2 人类决策权威
用户始终拥有最终决定权。在任何权限模式下,用户都可以通过终端对话框批准或拒绝操作。即使在 auto 模式下,当分类器(Classifier)判定需阻止操作时,用户仍可通过交互式对话框覆盖分类器决定。
系统通过以下机制保障人类决策权威:
- 建议系统(
generateSuggestions,src/utils/permissions/filesystem.ts:1414-1473):当询问用户时,同时提供可操作的建议(如"允许本次会话的所有编辑"、"添加目录到工作区") - 权限解释器(
PermissionExplainer,src/utils/permissions/permissionExplainer.ts):使用 Haiku 模型解释命令的风险等级(LOW/MEDIUM/HIGH)、目的和潜在风险,辅助用户决策 - 渐进式暴露:只对高风险操作发起询问,低风险操作在适当模式下自动批准
1.3 防御纵深
系统采用多层重叠的安全机制,而非依赖单一安全边界。这意味着即使某一层被绕过,后续层仍能提供保护。
┌─────────────────────────────────────────────────────┐
│ 防御纵深架构 │
├─────────────────────────────────────────────────────┤
│ 1. 规则系统 (deny/allow/ask rules) │
│ 2. PreToolUse Hook (用户自定义拦截) │
│ 3. 路径安全检查 (Dangerous Files, Windows 模式) │
│ 4. 模式驱动的权限处理 (Mode-based Decision) │
│ 5. 自动模式分类器 (Auto Mode Classifier) │
│ 6. 用户确认对话框 (User Confirmation Dialog) │
│ 7. 沙箱 (Sandbox) 容器化执行 │
│ 8. 拒绝跟踪 (Denial Tracking) 防滥用 │
└─────────────────────────────────────────────────────┘
1.4 渐进信任
系统支持用户通过时间建立信任轨迹。随着用户批准更多操作,系统会逐步提高自动化程度:
- 默认模式:auto-approve rate 起始约 20%,每一步都需确认
- auto 模式改进:系统通过拒绝跟踪(
denialTracking.ts)记录分类器连续拒绝次数,超过阈值(连续 3 次或总计 20 次)后回退到用户询问模式 - 规则积累:用户可逐步添加
alwaysAllow规则(如Bash(ls:*)),减少未来对低风险操作的询问 - acceptEdits 模式:一旦用户选择此模式,工作目录内的所有文件编辑操作自动批准
2. 七种权限模式
权限模式定义在 src/types/permissions.ts:16-36,运行时配置在 src/utils/permissions/PermissionMode.ts:42-91。
模式总览
| 模式 | 内部名称 | 符号 | 自动批准 | 需要确认 | 风险等级 |
|---|---|---|---|---|---|
| 默认 | default |
— | 无 | 所有操作 | 低(最安全) |
| 接受编辑 | acceptEdits |
⏵⏵ | 工作目录内文件编辑 | Bash 命令、目录外写入、MCP | 中低 |
| 计划 | plan |
⏸ | 读操作(文件读取、搜索) | 写操作、Bash 命令 | 低 |
| 自动 | auto |
⏵⏵ | 分类器批准的 + 安全放行列表 | 分类器阻止的 | 中 |
| 不询问 | dontAsk |
⏵⏵ | 无(所有 ask → deny) | 无法操作 | 高 |
| 绕过权限 | bypassPermissions |
⏵⏵ | 所有操作(除安全检查外) | 安全检查(.git/ 等) | 最高 |
| Bubble | bubble |
— | Ant 内部使用 | Ant 内部使用 | — |
2.1 Default(默认模式)
文件: src/utils/permissions/PermissionMode.ts:45-50
default: {
title: 'Default',
shortTitle: 'Default',
symbol: '',
color: 'text',
external: 'default',
}
- 行为: 每一步操作都需要用户确认
- 自动批准: 无
- 询问: 所有工具调用
- 适用场景: 新项目、不信任 AI 操作时
- 风险: 最低,但效率也最低
2.2 AcceptEdits(接受编辑模式)
文件: src/utils/permissions/PermissionMode.ts:59-65
acceptEdits: {
title: 'Accept edits',
shortTitle: 'Accept',
symbol: '⏵⏵',
color: 'autoAccept',
external: 'acceptEdits',
}
- 行为: 自动批准工作目录内的文件编辑操作(
FileEditTool,FileWriteTool) - 自动批准: 工作目录(
getOriginalCwd()已在additionalWorkingDirectories中的路径)内的文件写入 - 询问: Bash 命令、工作目录外的文件写入、MCP 工具、网络操作
- 实现参考:
src/utils/permissions/filesystem.ts:1360-1375— 当mode === 'acceptEdits'且路径在工作目录内时,直接返回allow - 适用场景: 用户希望 AI 可以直接修改代码,但不想让其执行任意命令
2.3 Plan(计划模式)
文件: src/utils/permissions/PermissionMode.ts:52-58
plan: {
title: 'Plan Mode',
shortTitle: 'Plan',
symbol: PAUSE_ICON,
color: 'planMode',
external: 'plan',
}
- 行为: 只读模式 + 计划讨论
- 自动批准: 所有读操作(文件读取、搜索、列表等)
- 询问: 文件编辑、Bash 命令、网络操作
- 内部机制: 当从 auto 模式进入 plan 时,auto 分类器仍在后台运行(
prePlanMode记录),退出 plan 时恢复 auto 状态(src/utils/permissions/permissionSetup.ts:1462-1493) - 适用场景: 探索代码库、制定重构计划、代码审查
与 Auto 模式的联动(permissionSetup.ts:1446-1455):
当用户已选择加入 auto 模式且 useAutoModeDuringPlan 启用时,plan 模式下分类器仍处于激活状态。这通过 shouldPlanUseAutoMode() 函数判断。
2.4 Auto(自动模式)
文件: src/utils/permissions/PermissionMode.ts:80-90
...(feature('TRANSCRIPT_CLASSIFIER')
? {
auto: {
title: 'Auto mode',
shortTitle: 'Auto',
symbol: '⏵⏵',
color: 'warning',
external: 'default',
},
}
: {}),
- 行为: 使用 AI 分类器自动审批操作
- 内部依赖:
feature('TRANSCRIPT_CLASSIFIER')— 编译期 feature flag,外部构建中通过 DCE(死代码消除)完全移除 - 自动批准:
acceptEdits快速路径可批准的(工作目录内编辑)- 安全放行列表中的工具(
isAutoModeAllowlistedTool,classifierDecision.ts:96-100) - 分类器判定为安全的操作
- 询问: 分类器判定为需阻止的操作(可被用户覆盖)
- 拒绝限制: 连续 3 次阻止 → 回退到询问;总计 20 次阻止 → 重置并回退(
denialTracking.ts:12-14)
2.5 DontAsk(不询问模式)
文件: src/utils/permissions/PermissionMode.ts:73-79
dontAsk: {
title: "Don't Ask",
shortTitle: 'DontAsk',
symbol: '⏵⏵',
color: 'error',
external: 'dontAsk',
}
- 行为: 将所有
ask决策转换为deny - 转换:
src/utils/permissions/permissions.ts:505-518if (appState.toolPermissionContext.mode === 'dontAsk') { return { behavior: 'deny', decisionReason: { type: 'mode', mode: 'dontAsk' }, message: DONT_ASK_REJECT_MESSAGE(tool.name), } } - 适用场景: 测试、CI/CD 环境或不想让 AI 执行任何操作的场景
2.6 BypassPermissions(绕过权限模式)
文件: src/utils/permissions/PermissionMode.ts:66-72
- 行为: 绕过所有权限检查(除安全检查和内容特定 ask 规则外)
- 禁用机制: 可通过 Statsig 门控
tengu_disable_bypass_permissions_mode或 settings 中permissions.disableBypassPermissionsMode完全禁用(permissionSetup.ts:695-711) - 例外:
bypassPermissions不影响requiresUserInteraction检查(permissions.ts:1231-1236)、安全检查(permissions.ts:1255-1260)和内容特定 ask 规则(permissions.ts:1244-1250)
2.7 Bubble(气泡模式)
- 用途: Ant 内部使用模式,外部构建不可见(
PermissionMode.ts:104中process.env.USER_TYPE !== 'ant'时被排除) - 文档: 无外部可用信息
3. 授权管道 (Authorization Pipeline)
核心实现位于 src/utils/permissions/permissions.ts 的 hasPermissionsToUseToolInner() 函数(第 1158-1319 行)和 hasPermissionsToUseTool()(第 473-956 行)。
管道全景图
用户输入工具调用
│
▼
┌─────────────────────────────────────┐
│ 1. 预过滤 │
│ 1a. 全局 deny 规则检查 │
│ 1b. 全局 ask 规则检查 │
│ 1c. 工具特定 checkPermissions() │
│ 1d. 工具实现 deny │
│ 1e. requiresUserInteraction 检查 │
│ 1f. 内容特定 ask 规则检查 │
│ 1g. 安全检查 (safety check) │
└─────────────────┬───────────────────┘
│ (2a-2b)
▼
┌─────────────────────────────────────┐
│ 2. 权限处理 │
│ 2a. bypassPermissions 模式放行 │
│ 2b. 全局 allow 规则放行 │
│ 2c. passthrough → ask 转换 │
└─────────────────┬───────────────────┘
│ (返回 ask/allow/deny)
▼
┌─────────────────────────────────────┐
│ 3. 模式转换 (在 hasPermissionsToUse) │
│ 3a. dontAsk 模式: ask → deny │
│ 3b. Auto 模式: │
│ ├ 非分类器可批准的安全检查: 返回 │
│ ├ PowerShell 默认拒绝 │
│ ├ acceptEdits 快速路径 │
│ ├ 安全放行列表 │
│ └ YOLO 分类器评估 │
│ 3c. 无提示模式: hooks → auto-deny│
└─────────────────┬───────────────────┘
│
▼
┌─────────────────┐
│ 用户确认对话框 │
│ (useCanUseTool) │
└─────────────────┘
3.1 第 1 步:规则预过滤
在 hasPermissionsToUseToolInner()(第 1158-1260 行)中执行:
1a. 全局 Deny 规则(第 1171-1181 行):
const denyRule = getDenyRuleForTool(appState.toolPermissionContext, tool)
if (denyRule) { return { behavior: 'deny', message: `Permission to use ${tool.name} has been denied.` } }
1b. 全局 Ask 规则(第 1184-1206 行):检查工具是否在 alwaysAsk 列表中。例外:如果 sandbox 启用且 autoAllowBashIfSandboxed 为 true,sandbox 内运行的 Bash 命令可跳过此检查。
1c. 工具特定权限检查(第 1214-1223 行):
const parsedInput = tool.inputSchema.parse(input)
toolPermissionResult = await tool.checkPermissions(parsedInput, context)
每个工具实现自己的 checkPermissions() 方法。例如 BashTool 实现命令级规则(前缀匹配、通配符匹配)。
1d. 工具实现 Deny(第 1226-1228 行):如果工具的 checkPermissions 返回 deny,直接返回。
1e. 用户交互要求(第 1231-1236 行):如果工具标记为 requiresUserInteraction(),即使是 bypassPermissions 模式也必须询问。
1f. 内容特定 Ask 规则(第 1244-1250 行):如 Bash(npm publish:*) 这样的规则,即使是 bypassPermissions 也必须尊重。
1g. 安全检查(第 1255-1260 行):checkPathSafetyForAutoEdit 返回的安全检查(如 .git/、.claude/、shell 配置文件)是 bypass-immune 的。
3.2 第 2 步:权限处理
2a. bypassPermissions 模式(第 1268-1281 行):直接放行所有操作(1a-1g 已过滤的危险操作除外)。
2b. 全局 Allow 规则(第 1284-1297 行):如果工具在 alwaysAllow 列表中,直接放行。
2c. passthrough → ask 转换(第 1300-1310 行):如果 checkPermissions 返回 passthrough,转换为 ask。
3.3 第 3 步:模式转换
在 hasPermissionsToUseTool()(第 473-956 行)中执行:
3a. dontAsk: 将所有 ask 转换为 deny(第 505-518 行)。
3b. Auto 模式: 复杂的分类器驱动审批流程(第 520-926 行):
- 非分类器可批准的安全检查 → 保持询问
requiresUserInteraction()的工具 → 保持询问- 拒绝跟踪检查
- PowerShell 默认拒绝(除非
POWERSHELL_AUTO_MODE特性启用) - acceptEdits 快速路径: 如果当前操作在 acceptEdits 模式下会被批准,直接放行(第 600-655 行)
- 安全放行列表:
isAutoModeAllowlistedTool()中的工具(第 660-685 行) - 分类器评估:
classifyYoloAction()执行完整的安全评估(第 692-926 行)
3c. 无提示模式: shouldAvoidPermissionPrompts 为 true 时(后台/headless agent),执行 PermissionRequest hooks,若 hook 未决定则 auto-deny(第 932-952 行)。
3.4 CLI Hook 整合
在 src/hooks/useCanUseTool.tsx 中,结果经过:
hasPermissionsToUseTool()管道- 如果
allow→ 立即批准,记录分类器批准信息 - 如果
deny→ 拒绝,记录 auto mode denial,发送通知 - 如果
ask→ 进入交互式权限处理流程:awaitAutomatedChecksBeforeDialog→ 协调器处理- Swarm Worker 处理(
handleSwarmWorkerPermission) - 竞争性分类器检查(Bash 命令的投机性分类器评估,
peekSpeculativeClassifierCheck) - 最终
handleInteractivePermission显示对话框
4. Auto-Mode 分类器
4.1 架构概览
Auto mode 分类器是系统的核心智能审批组件,使用 Claude 模型(默认使用主循环模型,可被 CLAUDE_CODE_AUTO_MODE_MODEL 环境变量或 GrowthBook 配置覆盖)对每个待审批的工具调用进行安全评估。
核心文件:
src/utils/permissions/yoloClassifier.ts— 分类器主逻辑、2 阶段 XML 分类器src/utils/permissions/classifierDecision.ts— 安全放行列表src/utils/permissions/classifierShared.ts— 共享解析工具src/utils/permissions/yolo-classifier-prompts/— 系统提示模板
4.2 两阶段分类器
系统支持三种分类器模式,通过 GrowthBook tengu_auto_mode_config.twoStageClassifier 配置:
| 模式 | 说明 | 适用场景 |
|---|---|---|
both (默认) |
Stage 1 (fast) + Stage 2 (thinking) | 平衡速度与准确率 |
fast |
仅 Stage 1,max_tokens=256,最终裁定 | 追求速度 |
thinking |
仅 Stage 2,跳过 Stage 1 | 需要深度推理 |
Stage 1 - 快速判断(yoloClassifier.ts:769-858):
max_tokens=64(fast-only 模式为 256)stop_sequences=['</block>']— 一旦输出</block>立即停止- 后缀:
"\nErr on the side of blocking. <block> immediately." - 如果 stage 1 判定允许(
<block>no</block>),直接返回 - 如果 stage 1 判定阻止(
<block>yes</block>),进入 stage 2
Stage 2 - 深度推理(yoloClassifier.ts:861-940):
max_tokens=4096- 包含
xml_s2_t2后缀,要求 chain-of-thought 推理 - 可包含
<thinking>标签 - 最终输出
<block>yes/no</block>和可选的<reason>...</reason>
4.3 双阶段上下文预算
┌──────────────────────────────────────────────────────┐
│ 上下文窗口管理 │
├──────────────────────────────────────────────────────┤
│ System Block (cache_control) ← 稳定缓存前缀 │
│ └ 基础提示 + 权限模板 + 用户 allow/deny 规则 │
│ │
│ CLAUDE.md (cache_control) ← 静态用户指令 │
│ │
│ Transcript Entries │
│ ├ User: "..." │
│ ├ Bash ls │
│ ├ User: "..." │
│ └ ... │
│ │
│ Action (cache_control) ← 正在评估的操作 │
│ └ 只有 system + CLAUDE.md + action 三个 cache_control│
│ block(不超过 API 限制的 4 个) │
└──────────────────────────────────────────────────────┘
4.4 评估指标与风险分类
官方数据:
- False-negative rate:17%(即分类器应阻止但未阻止的操作比例)
- 第三方测试 FNR:70.3%(表明在更复杂场景下分类器有显著漏报)
四种风险类别(来自系统提示模板 auto_mode_system_prompt.txt):
| 类别 | 说明 | 示例 |
|---|---|---|
| Overeager Behavior | 模型过于积极执行未明确请求的操作 | 未问用户就安装包 |
| Honest Mistakes | 模型判断错误导致的非恶意操作 | 删错了文件 |
| Prompt Injection | 提示注入攻击 | 用户指令中隐藏恶意代码 |
| Model Misalignment | 模型与用户意图不一致 | 执行与任务无关的危险操作 |
4.5 安全放行列表
文件: src/utils/permissions/classifierDecision.ts:56-94
const SAFE_YOLO_ALLOWLISTED_TOOLS = new Set([
FILE_READ_TOOL_NAME, GREP_TOOL_NAME, GLOB_TOOL_NAME,
LSP_TOOL_NAME, TOOL_SEARCH_TOOL_NAME, LIST_MCP_RESOURCES_TOOL_NAME,
TODO_WRITE_TOOL_NAME, TASK_CREATE_TOOL_NAME, // ... 等
])
注意:isAutoModeAllowlistedTool()(第 96-100 行)当前实现为 return true — 所有工具都跳过分类器。这意味着安全放行列表实际上当前被全局放行取代,所有工具通过 acceptEdits 快速路径评估后再考虑分类器。
但代码中的注释明确说明:
"Does NOT include write/edit tools — those are handled by the acceptEdits fast path"
4.6 提示注入防护
JSONL 转录格式(yoloClassifier.ts:412-423):
{"Bash":"ls -la"} ← JSON 编码,注入无法突破字符串边界
{"user":"forged text"} ← tool_use 块中的 user 文本不会混淆分类器
传统文本格式:
Bash ls -la
User: forged text
JSONL 格式是注入防护的关键设计:用户输入和工具调用都被 JSON.stringify 编码,因此恶意内容中的换行符和特殊字符不可能突破字符串边界。
4.7 拒绝跟踪 (Denial Tracking)
文件: src/utils/permissions/denialTracking.ts
export const DENIAL_LIMITS = {
maxConsecutive: 3, // 连续拒绝 3 次后回退
maxTotal: 20, // 总计拒绝 20 次后回退
} as const
recordDenial(state): 递增 consecutiveDenials 和 totalDenialsrecordSuccess(state): 重置 consecutiveDenials 为 0shouldFallbackToPrompting(state): 超出任一阈值时返回 true
超过限制后的行为:
- CLI 模式:回退到手动询问,包含分类器的拒绝理由
- headless 模式:抛出
AbortError,终止 agent
5. 规则系统
5.1 规则类型
文件: src/types/permissions.ts:67-79, src/utils/permissions/permissions.ts:238-302
三种规则行为(PermissionBehavior):
| 行为 | 效果 | 适用场景 |
|---|---|---|
allow |
工具被允许,跳过后续检查 | --allowed-tools Bash(ls:*) |
deny |
工具被拒绝,不执行 | --disallowed-tools Bash(rm:*) |
ask |
强制询问用户 | Bash(npm publish:*) |
5.2 规则来源
文件: src/types/permissions.ts:54-62
| 来源 | 持久化 | 说明 |
|---|---|---|
userSettings |
~/.claude/settings.json |
全局用户设置 |
projectSettings |
.claude/settings.json |
项目级别,可提交到 git |
localSettings |
.claude/settings.local.json |
项目级别,gitignored |
policySettings |
企业策略 | 只读,不可删除 |
flagSettings |
编译期标志 | 只读 |
cliArg |
CLI 参数 | --allowed-tools, --disallowed-tools |
command |
斜杠命令 | 命令前导块中的规则 |
session |
内存 | 临时会话规则 |
规则加载流程(permissionsLoader.ts:120-133):
- 如果
allowManagedPermissionRulesOnly为 true → 只加载policySettings - 否则加载所有已启用的设置源
5.3 路径模式匹配
文件: src/utils/permissions/filesystem.ts:960-1025
使用 gitignore 风格的 ignore 库进行路径匹配:
export function matchingRuleForInput(
path: string,
toolPermissionContext: ToolPermissionContext,
toolType: 'edit' | 'read',
behavior: 'allow' | 'deny' | 'ask',
): PermissionRule | null
关键要点:
- 路径被规范化为 POSIX 格式(
relativePath函数,第 170-179 行) - 双斜杠前缀
//表示相对于根/的路径 - 波浪号前缀
~/表示相对于用户主目录的路径 - 模式
/**后缀被简化为匹配目录本身及其所有子项 - 大小写标准化(
normalizeCaseForComparison)防止大小写绕过
5.4 路径安全检查
文件: src/utils/permissions/filesystem.ts:620-665
checkPathSafetyForAutoEdit() 检查:
Windows 可疑路径模式(
hasSuspiciousWindowsPathPattern,filesystem.ts:537-602):- NTFS Alternate Data Streams(
:号,仅 Windows/WSL) - 8.3 短文件名(
~后跟数字) - 长路径前缀(
\\?\,//?/等) - 尾部点和空格(
.git.,.claude.) - DOS 设备名(
CON,PRN,AUX等) - 三个连续点(
...) - UNC 路径(
\\server\share)
- NTFS Alternate Data Streams(
Claude 配置文件(
isClaudeConfigFilePath,filesystem.ts:225-242):settings.json,settings.local.json.claude/commands/,.claude/agents/,.claude/skills/
危险文件(
DANGEROUS_FILES,filesystem.ts:57-68):.gitconfig,.gitmodules.bashrc,.bash_profile,.zshrc,.zprofile,.profile.ripgreprc,.mcp.json,.claude.json
危险目录(
DANGEROUS_DIRECTORIES,filesystem.ts:74-79):.git,.vscode,.idea,.claude
安全检查也适用于解析后的符号链接路径,防止通过符号链接绕过。
5.5 危险权限检测
文件:
src/utils/permissions/permissionSetup.ts:94-285src/utils/permissions/dangerousPatterns.ts
进入 auto 模式时,系统自动检测并剥离(strip)危险权限:
危险 Bash 规则(isDangerousBashPermission):
// 完全通配: Bash, Bash(*), Bash() → 允许所有命令 → 危险
// 解释器通配: Bash(python:*) → 允许任意 Python 代码 → 危险
// 包管理器: Bash(npm run:*) → 可执行任意脚本 → 危险
匹配模式列表(dangerousPatterns.ts):
CROSS_PLATFORM_CODE_EXEC = [
'python', 'python3', 'node', 'deno', 'ruby', 'perl', 'php', 'lua',
'npx', 'bunx', 'npm run', 'yarn run', 'pnpm run', 'bun run',
'bash', 'sh', 'ssh',
]
// ant-only: gh, curl, wget, git, kubectl, aws, gcloud, gsutil
危险 PowerShell 规则(isDangerousPowerShellPermission, permissionSetup.ts:157-233):
额外包含 iex, invoke-expression, start-process, add-type, new-object 等。
危险 Agent 规则(isDangerousTaskPermission):
任何 Agent 工具的 allow 规则都会绕过分类器对子 agent 的评估。
5.6 影子规则检测
文件: src/utils/permissions/shadowedRuleDetection.ts
检测 allow 规则是否被 ask/deny 规则"屏蔽"(即永远无法生效):
// 示例: allow 规则 Bash(ls:*) 被全局 ask 规则 Bash 屏蔽
// 因为 ask 规则在评估顺序中先于 allow 规则
- Deny 屏蔽: 工具级别的 deny 规则使该工具的所有 allow 规则不可达
- Ask 屏蔽: 工具级别的 ask 规则使带内容的 allow 规则不可达(总是会先询问)
- 沙箱例外: 如果 sandbox 启用且
autoAllowBashIfSandboxed为 true,个人设置的 ask 规则不屏蔽 allow 规则
6. Shell 沙箱
6.1 容器化执行环境
文件: src/utils/sandbox/sandbox-adapter.ts
SandboxManager 提供容器化的命令执行环境,支持:
- 文件系统隔离:
getFsReadConfig()/getFsWriteConfig()控制可读写的路径 - 网络隔离:
getNetworkRestrictionConfig()控制网络访问 - 命令排除:
excludedCommands列表中的命令不被沙箱化
6.2 AutoAllowBashIfSandboxed
文件: src/utils/sandbox/sandbox-adapter.ts:469-472
function isAutoAllowBashIfSandboxed(): boolean {
const settings = getSettings_DEPRECATED()
return settings?.sandbox?.autoAllowBashIfSandboxed ?? true // 默认开启
}
当此选项启用时:
- 所有 Bash 命令在沙箱内运行
- Bash 命令的权限检查被自动放行(
permissions.ts:1189-1193) - 命令不在沙箱内运行时(
dangerouslyDisableSandbox = true或excludedCommands),仍遵循正常权限检查
6.3 平台支持
支持通过 enabledPlatforms 配置控制哪些平台启用沙箱:
// src/entrypoints/sandboxTypes.ts:108-112
// 为了 NVIDIA 企业部署,最初仅 macOS 启用沙箱
// Linux/WSL 沙箱支持较新,在扩展前需要更多验证
6.4 Sandbox 写允许列表
在路径验证中(pathValidation.ts:101-123),当沙箱启用时,沙箱配置的写允许列表作为额外的工作目录:
export function isPathInSandboxWriteAllowlist(resolvedPath: string): boolean {
const { allowOnly, denyWithinAllow } = SandboxManager.getFsWriteConfig()
// 检查路径是否在 allowOnly 中且不在 denyWithinAllow 中
}
7. 安全相关实现细节
7.1 Protected Paths
文件: src/utils/permissions/filesystem.ts
系统定义了多层保护路径,防止 AI 修改关键配置:
内部可编辑路径(checkEditableInternalPath, filesystem.ts:1479-1605)——自动允许编辑:
- 当前会话的计划文件(
isSessionPlanFile) - 临时目录(
isScratchpadPath) - 模板任务目录(
CLAUDE_JOB_DIR,feature('TEMPLATES')时) - Agent 记忆目录(
isAgentMemoryPath) - 自动记忆目录(
isAutoMemPath,无覆盖路径时) .claude/launch.json(桌面预览配置)
内部可读路径(checkReadableInternalPath, filesystem.ts:1611-1777)——自动允许读取:
- 会话记忆目录(
isSessionMemoryPath) - 项目目录(
isProjectDirPath) - 计划文件
- 工具结果目录(
getToolResultsDir) - 临时目录
- Agent 记忆目录
- 任务目录(
.claude/tasks/) - 团队目录(
.claude/teams/) - 内置技能参考文件(
getBundledSkillsRoot)
7.2 Hook 系统
文件: src/utils/hooks.ts:4157-4192
PreToolUse Hooks
executePermissionRequestHooks() 在权限检查流程中调用,提供用户自定义的拦截逻辑:
export async function* executePermissionRequestHooks<ToolInput>(
toolName: string,
toolUseID: string,
toolInput: ToolInput,
toolUseContext: ToolUseContext,
permissionMode?: string,
permissionSuggestions?: PermissionUpdate[],
signal?: AbortSignal,
timeoutMs: number = TOOL_HOOK_EXECUTION_TIMEOUT_MS,
// ...
)
Hook 可以返回:
{ behavior: 'allow', updatedInput, updatedPermissions }→ 放行(含输入修改和权限更新){ behavior: 'deny', message, interrupt }→ 拒绝(含可选的中断信号)
使用场景:
- headless/async agent 不能显示权限提示框时通过 hook 授权
- CI/CD 环境中的自动化策略执行
PostToolUse Hooks
代码中存在 PostToolUse 钩子类型(在 hook 类型定义中),用于工具执行后的审计、日志记录和副作用处理。
7.3 远程会话权限桥接
文件: src/utils/permissions/permissionSetup.ts:748-758
当 CLAUDE_CODE_REMOTE 环境变量设置时,运行在远程桥接模式,权限模式受限:
if (
isEnvTruthy(process.env.CLAUDE_CODE_REMOTE) &&
!['acceptEdits', 'plan', 'default'].includes(settingsMode)
) {
// 只有 acceptEdits、plan、default 模式支持 CCR
logEvent('tengu_ccr_unsupported_default_mode_ignored', { mode: settingsMode })
}
这意味着在远程会话中,bypassPermissions 和 auto 模式被禁用,增强了对远程连接的安全控制。
7.4 权限解释器
文件: src/utils/permissions/permissionExplainer.ts
在询问用户时,系统可调用 Haiku 模型生成操作的风险评估:
export async function generatePermissionExplanation({
toolName, toolInput, toolDescription, messages, signal,
}): Promise<PermissionExplanation | null>
输出包含:
riskLevel:LOW|MEDIUM|HIGHexplanation: 操作说明(1-2 句话)reasoning: 执行此操作的原因risk: 可能的风险(15 字以内)
此功能可通过 permissionExplainerEnabled 配置禁用。
7.5 权限更新的持久化
文件: src/utils/permissions/PermissionUpdate.ts, PermissionUpdateSchema.ts
权限变更(添加规则、删除规则、更改模式、添加目录)通过 permissionRuleParser.ts 进行序列化和反序列化,并写入到对应的设置文件。
export type PermissionUpdate =
| { type: 'addRules'; destination: PermissionUpdateDestination; rules: PermissionRuleValue[]; behavior: PermissionBehavior }
| { type: 'replaceRules'; destination: ...; rules: ...; behavior: ... }
| { type: 'removeRules'; destination: ...; rules: ...; behavior: ... }
| { type: 'setMode'; destination: ...; mode: ExternalPermissionMode }
| { type: 'addDirectories'; destination: ...; directories: string[] }
| { type: 'removeDirectories'; destination: ...; directories: string[] }
7.6 特征标识与死代码消除
整个权限系统大量使用 Bun 编译期的 feature() 函数进行条件编译:
| Feature Flag | 控制的特性 | 文件 |
|---|---|---|
TRANSCRIPT_CLASSIFIER |
整个 auto 模式系统(分类器、状态管理、危险权限剥离) | 多处 |
BASH_CLASSIFIER |
Bash 命令提示词分类器 | bashClassifier.ts |
POWERSHELL_AUTO_MODE |
PowerShell 自动模式 | yoloClassifier.ts |
TEMPLATES |
模板任务目录自动编辑 | filesystem.ts |
这样,外部构建中所有 auto 模式相关代码被完全消除,减小了二进制体积并简化了安全模型。
7.7 拒绝统计与滥用防护
文件: src/utils/permissions/denialTracking.ts
export const DENIAL_LIMITS = {
maxConsecutive: 3, // 连续 3 次拒绝 → 回退到手动
maxTotal: 20, // 总计 20 次拒绝 → 回退到手动
} as const
拒绝跟踪统计记录在 AppState.denialTracking 中,在 auto 模式下每次分类器判定后更新:
- 成功(allow)→
recordSuccess()→ 重置连续拒绝计数 - 失败(block)→
recordDenial()→ 递增两个计数器 - 超出阈值 →
shouldFallbackToPrompting()→ 回退到交互式询问
headless agent 模式下,超出限制直接终止 agent(permissions.ts:1023-1027)。
7.8 CLI 与 Settings 初始化流程
文件: src/utils/permissions/permissionSetup.ts:872-1033
initializeToolPermissionContext() 的完整流程:
- 解析
--allowed-tools,--disallowed-tools,--base-toolsCLI 参数 - 检测
bypassPermissions模式是否可用(Statsig 门控 + settings 检查) - 从磁盘加载所有权限规则(
loadAllPermissionRulesFromDisk) - 检测危险和过宽的 Shell 权限
- 应用规则到
ToolPermissionContext - 处理工作目录的符号链接(
process.env.PWD与getOriginalCwd()的差异) - 验证并添加附加目录(
--add-dir)
附录 A:关键文件索引
| 文件 | 职责 | 关键行 |
|---|---|---|
src/types/permissions.ts |
权限类型定义 | 16-36 (模式), 54-62 (规则来源), 75-79 (规则), 271-324 (DecisionReason) |
src/utils/permissions/PermissionMode.ts |
权限模式配置与 UI 展示 | 42-91 (模式配置), 97-105 (外部模式过滤) |
src/utils/permissions/permissions.ts |
核心权限检查管道 | 473-956 (hasPermissionsToUseTool), 1158-1319 (hasPermissionsToUseToolInner), 1060-1156 (checkRuleBasedPermissions) |
src/utils/permissions/filesystem.ts |
文件系统权限检查 | 57-68 (危险文件), 74-79 (危险目录), 620-665 (安全检查), 960-1025 (路径匹配), 1479-1605 (内部可编辑路径), 1611-1777 (内部可读路径) |
src/utils/permissions/pathValidation.ts |
路径验证 | 101-123 (沙箱写允许), 141-263 (isPathAllowed), 331-367 (危险删除路径), 373-485 (validatePath) |
src/utils/permissions/yoloClassifier.ts |
Auto 模式分类器 | 711-996 (XML 2 阶段分类器), 1012-1306 (classifyYoloAction), 1484-1495 (formatActionForClassifier) |
src/utils/permissions/classifierDecision.ts |
分类器决策与放行列表 | 56-94 (SAFE_YOLO_ALLOWLISTED_TOOLS), 96-100 (isAutoModeAllowlistedTool) |
src/utils/permissions/permissionSetup.ts |
模式切换、危险权限剥离 | 94-147 (isDangerousBashPermission), 157-233 (isDangerousPowerShellPermission), 510-553 (stripDangerousPermissionsForAutoMode), 597-646 (transitionPermissionMode), 872-1033 (initializeToolPermissionContext) |
src/utils/permissions/permissionsLoader.ts |
规则加载与持久化 | 120-133 (loadAllPermissionRulesFromDisk), 229-296 (addPermissionRulesToSettings) |
src/utils/permissions/shellRuleMatching.ts |
Shell 规则解析与匹配 | 43-48 (prefix 提取), 90-153 (通配符匹配) |
src/utils/permissions/denialTracking.ts |
拒绝跟踪 | 12-15 (DENIAL_LIMITS), 40-44 (shouldFallbackToPrompting) |
src/utils/permissions/shadowedRuleDetection.ts |
影子规则检测 | 193-234 (detectUnreachableRules) |
src/utils/permissions/dangerousPatterns.ts |
危险命令模式 | 18-42 (CROSS_PLATFORM_CODE_EXEC), 44-80 (DANGEROUS_BASH_PATTERNS) |
src/utils/permissions/permissionExplainer.ts |
权限解释器 | 147-250 (generatePermissionExplanation) |
src/hooks/useCanUseTool.tsx |
React 权限 hook | 28-203 (useCanUseTool) |
src/utils/sandbox/sandbox-adapter.ts |
沙箱适配器 | 469-472 (isAutoAllowBashIfSandboxed) |
src/utils/hooks.ts |
Hook 执行系统 | 4157-4192 (executePermissionRequestHooks) |
src/utils/permissions/bypassPermissionsKillswitch.ts |
绕过权限禁用开关 | 19-47 (checkAndDisableBypassPermissionsIfNeeded), 74-117 (checkAndDisableAutoModeIfNeeded) |
附录 B:数据流图(权限检查)
工具调用请求
│
▼
┌──────────────────────────────────────┐
│ hasPermissionsToUseTool() │
│ src/utils/permissions/permissions.ts│
│ │
│ 1. 规则预过滤段 │
│ ├── 全局 deny 规则 │
│ ├── 全局 ask 规则 (sandbox 快速放行)│
│ ├── tool.checkPermissions() │
│ ├── tool.requiresUserInteraction │
│ ├── 内容 ask 规则 │
│ └── 安全检查 │
│ │
│ 2. 权限处理段 │
│ ├── bypassPermissions / plan │
│ └── 全局 allow 规则 │
└──────────┬───────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ hasPermissionsToUseTool() — 模式后处理│
│ │
│ 3. dontAsk → ask→deny │
│ 4. Auto 模式: │
│ ├─ acceptEdits 快速路径 │
│ ├─ 放行列表 │
│ └─ YOLO 分类器评估 │
│ 5. Headless → hooks → auto-deny │
└──────────┬───────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ useCanUseTool (React Hook) │
│ src/hooks/useCanUseTool.tsx │
│ │
│ allow → 立即执行 │
│ deny → 拒绝 + 通知 │
│ ask → 交互式处理 │
│ ├─ 协调器检查 │
│ ├─ Swarm Worker 转发 │
│ ├─ 投机性分类器 (Bash) │
│ └─ 用户确认对话框 │
└──────────────────────────────────────┘
附录:AskUserQuestion 独立问答通道
AskUserQuestion 用于向用户提出多选题(澄清需求、在方案间做选择)。它不再借道权限系统的 ask 流程,而是走一条独立的问答通道,与权限确认队列解耦。
与权限 ask 的区别
| 维度 | 权限式 ask(早期实现) |
独立问答通道(当前实现) |
|---|---|---|
checkPermissions 返回 |
behavior: 'ask' |
behavior: 'allow' |
| 阻塞方式 | 权限回调回填 answers |
call 内 await questionService.ask(...) |
| 本地渲染 | 进 toolUseConfirmQueue,由 PermissionRequest 渲染 |
独立 overlay(QuestionPrompt),与 toolPermissionOverlay 并列 |
| 焦点协调 | tool-permission 对话框 |
question 对话框 + useRegisterOverlay('question') |
运行时组件
src/services/question/questionService.ts— 独立问答通道。ask(questions)存入pending: Map<id, ...>并返回 Promise;reply/reject解析对应 Promise;通过 EventEmitter 广播asked/replied/rejected。src/tools/AskUserQuestionTool/AskUserQuestionTool.tsx—call内await questionService.ask(...),再把结果按题映射为answers。src/components/question/QuestionPrompt.tsx— 独立 overlay,复用QuestionView/SubmitQuestionsView/use-multiple-choice-state渲染,并用useKeybindings(Tabs上下文)绑定多题切换。src/screens/REPL.tsx— 订阅asked弹出 overlay;订阅replied/rejected关闭 overlay。
桥接 / CCR 远程转发
桥接(BRIDGE_MODE)连接时,asked 事件会同时把问题作为 can_use_tool control_request 转发给远程用户(claude.ai),与本地 overlay 竞速:
- 远程
allow且带updatedInput.answers→ 按题映射后questionService.reply(...);通用allow(无 answers)降级为每题选第一个选项。 - 远程
deny→questionService.reject(...)。 - 任一端先应答,都会清掉本地 overlay 并
cancelRequest另一端的 prompt,避免残留。
注:因
checkPermissions现返回allow,AskUserQuestion 不再进入handleInteractivePermission,故不会与桥接路径重复转发。