权限与安全系统
三层纵深防御:权限规则引擎 → Hook 拦截决策 → 沙箱隔离
职责概述
解决的问题:AI 可以读写文件、执行命令、访问网络,但用户不可能每次都手动审批。需要一个权限系统自动判断"这个操作安不安全",安全的放行,可疑的询问,危险的拒绝。
应用场景:① 用户首次允许读取 src/ 目录后,后续读取自动放行 ② 模型尝试执行 rm -rf 时弹窗确认 ③ 项目级 .claude/settings.json 预设允许规则,免交互 ④ Hook 脚本在工具执行前后拦截和修改行为。
一句话理解:就像手机的权限管理——App 请求摄像头,第一次弹窗问你,你同意后以后就不再问了。但如果你设置里关了,它就永远用不了。
架构设计
核心数据流
权限决策流程(13 步链)
getDenyRuleForTool() 检查工具是否被 blanket deny。匹配到则直接返回 deny。来源:permissions.ts:1079getAskRuleForTool() 检查工具是否配置了 "每次询问"。沙箱启用且命令可沙箱化时跳过。来源:permissions.ts:1092tool.checkPermissions(parsedInput, context) 调用工具自身的权限逻辑(如 BashTool 的子命令规则匹配)。来源:permissions.ts:1114-1126tool.checkPermissions() 返回 deny,直接返回拒绝。Bash(npm publish:*))| 1g: 安全路径检查(.git/, .claude/ 等)——bypass 免疫。toolAlwaysAllowedRule() 检查工具是否有 blanket allow。匹配到则返回 allow。passthrough(无明确决策),转换为 ask——需要用户交互确认。安全层协作图
模型请求调用工具
permissions.ts:473 — 13 步规则引擎
PermissionContext.ts:96 — Hook + 交互
sandbox-adapter.ts — 进程隔离
规则放行 / bypass / allow 规则
deny 规则 / 安全检查 / dontAsk
交互式确认 / Hook 审批 / 分类器
关键类型与接口
PermissionMode 类型(types/permissions.ts:29)
// 外部可见的模式
type ExternalPermissionMode = 'default' | 'plan' | 'acceptEdits' | 'bypassPermissions' | 'dontAsk'
// 内部扩展模式(ant-only)
type InternalPermissionMode = ExternalPermissionMode | 'auto' | 'bubble'
type PermissionMode = InternalPermissionMode
ToolPermissionContext 类型(Tool.ts:123-138)
export type ToolPermissionContext = DeepImmutable<{
mode: PermissionMode
additionalWorkingDirectories: Map<string, AdditionalWorkingDirectory>
alwaysAllowRules: ToolPermissionRulesBySource // 按来源分组的 allow 规则
alwaysDenyRules: ToolPermissionRulesBySource // 按来源分组的 deny 规则
alwaysAskRules: ToolPermissionRulesBySource // 按来源分组的 ask 规则
isBypassPermissionsModeAvailable: boolean
isAutoModeAvailable?: boolean
strippedDangerousRules?: ToolPermissionRulesBySource // auto 模式剥离的危险规则
shouldAvoidPermissionPrompts?: boolean // 后台 agent 无 UI 时自动拒绝
awaitAutomatedChecksBeforeDialog?: boolean // coordinator worker 先等自动化检查
prePlanMode?: PermissionMode // plan 模式前的原始模式
}>
PermissionDecision 类型(types/permissions.ts:241-246)
export type PermissionDecision<
Input extends Record<string, unknown> = Record<string, unknown>,
> = PermissionAllowDecision<Input>
| PermissionAskDecision<Input>
| PermissionDenyDecision
PermissionDecisionReason 联合类型(types/permissions.ts:271-324)
export type PermissionDecisionReason =
| { type: 'rule'; rule: PermissionRule } // 规则匹配
| { type: 'mode'; mode: PermissionMode } // 模式级决策
| { type: 'safetyCheck'; classifierApprovable?: boolean } // 安全路径检查
| { type: 'user' } // 用户手动决策
| { type: 'hook'; hookOutput: HookOutput } // Hook 拦截
| { type: 'classifier'; ... } // AI 分类器
| { type: 'autoAccept' } // acceptEdits 快路径
| { type: 'asyncAgent'; reason: string } // 后台 agent 无 UI
| { type: 'denialLimit'; ... } // 连续拒绝上限
核心权限检查函数(permissions.ts:473)
export const hasPermissionsToUseTool: CanUseToolFn = async (
tool, input, context, assistantMessage, toolUseID,
): Promise<PermissionDecision> => {
const result = await hasPermissionsToUseToolInner(tool, input, context)
// 重置连续拒绝计数
if (result.behavior === 'allow') { /* ... */ return result }
// dontAsk 模式: ask → deny
if (result.behavior === 'ask') {
if (appState.toolPermissionContext.mode === 'dontAsk') {
return { behavior: 'deny', decisionReason: { type: 'mode', mode: 'dontAsk' } }
}
// auto 模式: 运行 AI 分类器
if (mode === 'auto' || (mode === 'plan' && isAutoModeActive())) {
// 安全检查 → acceptEdits 快路径 → classifier API 调用
// ...
}
}
return result
}
规则子集检查(permissions.ts:1071-1156)
export async function checkRuleBasedPermissions(
tool: Tool, input, context,
): Promise<PermissionAskDecision | PermissionDenyDecision | null> {
// 1a. getDenyRuleForTool → blanket deny
// 1b. getAskRuleForTool → blanket ask (沙箱自动放行例外)
// 1c. tool.checkPermissions(parsedInput, context) → 工具级检查
// 1d. deny → 返回拒绝
// 1f. 内容级 ask 规则
// 1g. safetyCheck(bypass 免疫)
return null // 无规则拒绝
}
沙箱配置转换(sandbox-adapter.ts:172)
export function convertToSandboxRuntimeConfig(
settings: SettingsJson,
): SandboxRuntimeConfig {
// 从 settings.permissions 提取网络域名规则
// 从 settings.sandbox 提取文件系统限制
// 组装为 SandboxRuntimeConfig {
// filesystem: { allowRead, denyRead, allowWrite, denyWrite, ... },
// network: { allowDomains, denyDomains, ... },
// ...
// }
}
设计模式与亮点
hasPermissionsToUseToolInner() 实现 13 步顺序决策,从 deny 规则(最严格)到 allow 规则(最宽松),中间包含工具级检查、安全路径检查、模式级放行。每步可短路返回。更多亮点
- Auto 模式 AI 分类器:在 auto 模式下,未命中明确规则的工具调用通过 AI 分类器决策。分类器异步运行,与用户交互竞争(resolve-once 模式)。分类器结果可被用户手动覆盖。
- 连续拒绝追踪:
denialTracking机制记录连续拒绝次数。达到上限后强制转为ask,防止 auto 模式陷入无限循环。异步子 agent 使用localDenialTracking(独立于主 AppState)。 - 沙箱自动放行:
autoAllowBashIfSandboxed设置允许在沙箱内执行的 Bash 命令跳过 ask 规则。沙箱提供了文件系统和网络限制,即使自动放行也有安全兜底。 - 危险权限剥离:
stripDangerousPermissionsForAutoMode()在进入 auto 模式时剥离过于宽泛的 allow 规则(如Bash(*)),防止分类器被绕过。剥离的规则保存在strippedDangerousRules中,退出 auto 模式时恢复。 - 多来源规则管理:
ToolPermissionRulesBySource按来源(userSettings、projectSettings、policySettings)分组管理规则,支持来源感知的持久化和 UI 展示。 - 三种权限处理器:
interactiveHandler(主 agent 交互式)、coordinatorHandler(协调器工作线程)、swarmWorkerHandler(Swarm 工作线程)针对不同运行环境定制权限处理逻辑。
开发者实践指南
为新工具添加权限控制
buildTool() 默认提供 checkPermissions: () => allow,将权限决策委托给通用系统。
checkPermissions() 提供工具级权限逻辑。参考 BashTool 的子命令匹配模式。
// 工具级权限检查示例
async checkPermissions(input, context): Promise<PermissionResult> {
const appState = context.getAppState()
const permissionCtx = appState.toolPermissionContext
// 检查是否有内容级 allow 规则
const rules = getRuleByContentsForTool(permissionCtx, this, 'allow')
for (const [pattern, rule] of rules) {
if (matchesPattern(input.command, pattern)) {
return { behavior: 'allow', updatedInput: input }
}
}
return { behavior: 'ask', message: `Allow ${this.name}?` }
}
权限模式切换
transitionPermissionMode()(permissionSetup.ts:597)处理所有模式转换,包含危险权限剥离/恢复逻辑。prepareContextForPlanMode()(permissionSetup.ts:1462)进入计划模式时保存当前模式到prePlanMode。verifyAutoModeGateAccess()(permissionSetup.ts:1078)异步验证 auto 模式可用性(GrowthBook flag + 设置检查)。
权限规则格式
// 工具级规则
"Bash" // 匹配整个工具
"Edit" // 匹配 FileEditTool
// 内容级规则(工具特定)
"Bash(git *)" // Bash: git 前缀命令
"Bash(npm run *)" // Bash: npm run 前缀
"Edit(/path/to/dir/**)" // Edit: 路径 glob 匹配
"WebFetch(domain.com)" // WebFetch: 域名匹配
"mcp__server__tool" // MCP 工具名匹配
"Agent(my-agent-type)" // Agent 类型匹配
架构师决策指南
设计权衡
采用固定的 13 步顺序检查而非可组合的责任链。优势:行为可预测,调试简单(每步有明确注释),性能好(无递归)。代价:新步骤需要修改核心函数,不够灵活。这是一个合理的权衡——权限逻辑需要确定性而非灵活性。
ToolPermissionContext 使用 DeepImmutable 包装,确保工具实现无法意外修改权限上下文。修改必须通过 applyPermissionUpdates() 和 setToolPermissionContext。这防止了权限状态的不一致性。
扩展性考量
- 新权限来源:
PermissionRuleSource联合类型('memory' | 'hooks' | 'userSettings' | 'projectSettings' | ...)可扩展。新增来源只需添加类型和对应的规则加载逻辑。 - 分类器扩展:
bashClassifier.ts在外部版本是空壳实现(所有方法返回 null/空)。Ant-only 版本通过 feature flagTRANSCRIPT_CLASSIFIER启用真正的 AI 分类器。 - 沙箱策略:
convertToSandboxRuntimeConfig()将 Claude Code 设置格式转换为SandboxRuntimeConfig。新增沙箱维度(如进程限制)需要同时修改此转换函数和上游设置类型。
性能考量
- 权限检查开销:
hasPermissionsToUseTool()在每个工具调用前执行。13 步检查大部分是 Map 查找和字符串匹配,开销可忽略。最重的操作是tool.checkPermissions()(BashTool 的子命令匹配)和 auto 模式的分类器 API 调用。 - 规则缓存:
getRuleByContentsForTool()返回 Map,避免重复解析规则内容。权限上下文通过applyPermissionUpdates()增量更新,不重建整个规则集。 - 分类器异步:AI 分类器通过
executeAsyncClassifierCheck()异步运行,与用户交互 UI 竞争。分类器结果先到达则自动放行,用户先操作则取消分类器。
安全考量
buildTool() 默认 isReadOnly: false、isConcurrencySafe: false;权限检查默认返回 ask(需用户确认)。只有在明确规则或模式下才允许自动放行。Bypass 免疫机制确保安全路径检查不可绕过。
◈ 可视化处理拓扑图
每次工具调用首先进入 7 步规则漏斗(Phase 1),前 4 步快速查找 O(1),后 3 步是安全免疫锚点。通过规则层后进入模式路由(Phase 2),根据 bypass/auto/default 等模式分派到不同决策路径。Auto 模式执行三层渐进决策(Phase 3),90%+ 的调用在前两层完成。
isAutoModeAllowlistedTool() 检查只读工具(Read、Glob 等)。permissions.ts:660classifyYoloAction(messages, action, ...) 独立 Haiku 模型分析工具名+参数安全性。permissions.ts:688-702① 分类器 DENY →
consecutiveDenials++② 超过阈值 → 升级到用户确认(不再自动)
③ 一次成功执行 →
recordSuccess() 重置计数器④ 恢复正常 auto 模式
防止提示注入攻击下的危险操作链
零 AI 调用
--dangerously-skip-permissions 也无法绕过。bypass 检查(2a)在安全路径检查(1g)之后执行,如果反序则攻击者可通过 bypass 跳过所有安全检查。⇉ 核心处理流程详解
权限系统是 Claude Code 安全模型的核心。每次工具调用都经过一条 13 步的权限检查管线,从规则匹配到模式判断再到 AI 分类器,层层过滤。hasPermissionsToUseTool 是外部入口,hasPermissionsToUseToolInner 执行核心逻辑,checkRuleBasedPermissions 提供规则子集的快速路径供 hooks 使用。
permissions.ts:473-479 接收工具、输入和上下文。L480 调用 hasPermissionsToUseToolInner() 获取核心决策。如果结果是 allow(L486-501),检查并重置连续拒绝计数器(auto 模式下,一次成功执行就打断拒绝连击)。如果结果是 ask,进入模式转换逻辑。permissions.ts:1171-1181 首先检查工具是否被全局拒绝规则(deny rule)匹配。getDenyRuleForTool()(L287-292)在 toolPermissionContext 的拒绝规则列表中查找匹配项。如果命中,立即返回 deny——这是最高优先级的判断,不可被任何后续步骤覆盖。permissions.ts:1184-1206 检查工具是否有全局 ask rule。getAskRuleForTool()(L297-302)查找匹配的"总是询问"规则。例外:当 Bash 工具启用了沙箱且沙箱自动放行开启时(L1189-1193),跳过 ask 规则,让后续的 Bash checkPermissions 按命令粒度处理。permissions.ts:1210-1223 调用 tool.checkPermissions(parsedInput, context)。先用 Zod schema 解析输入(L1215),然后委托给工具实现的具体检查逻辑。BashTool 会解析命令、匹配子命令规则、检查沙箱;EditTool 会检查文件路径安全性。返回的 toolPermissionResult 可能是 allow/deny/ask/passthrough。permissions.ts:1226-1260 执行一系列"免疫检查":工具实现的 deny(1d)、需要用户交互的工具(1e)、内容级 ask 规则(1f)和安全路径检查(1g)。安全路径检查(.git/、.claude/、.vscode/、shell 配置)是 bypass-immune 的——即使在 --dangerously-skip-permissions 模式下也必须提示用户。permissions.ts:1268-1281 检查当前权限模式:bypassPermissions 模式直接放行(但安全检查已在步骤 1g 过滤了危险路径)。plan 模式如果初始启动时带有 bypass,也放行。步骤 2b(L1284-1297)检查工具级 allow 规则——如果工具在白名单中,直接放行。permissions.ts:520-686 在 auto 模式下(L520-525),执行多层快速路径:先尝试 acceptEdits 模拟(L600-656,检查工具在 acceptEdits 模式下是否会被放行),再查安全工具白名单(L660),最后运行 AI 分类器(L688-702)。分类器使用独立的 Haiku 模型调用,分析工具名和参数判断安全性。分类器结果受连续拒绝计数器影响——拒绝过多会升级到用户确认。permissions.ts:1299-1318 将工具返回的 passthrough 行为转换为 ask(L1301-1309),表示"没有规则匹配,需要用户确认"。最终返回 PermissionDecision,包含 behavior(allow/deny/ask)、可选的 updatedInput(权限系统可能修改输入)和 decisionReason(决策原因用于审计和日志)。TOOL_DEFAULTS 中 checkPermissions 返回 allow(因为未实现自定义检查的工具按默认放行),但 hasPermissionsToUseToolInner 在无规则匹配时将 passthrough 转为 ask。安全检查(1g)是 bypass-immune 的——即使在 --dangerously-skip-permissions 模式下,修改 .git/ 或 shell 配置文件仍然会触发用户确认。★ 设计精华
1. 纵深防御 — 13 步管线中的不可绕过层
权限检查不是单一的"yes/no"判断,而是一条 13 步管线,其中某些步骤是不可绕过的"锚点"。步骤 1a(全局 deny)在任何模式之前执行,步骤 1g(安全路径检查)在 bypass 模式之后仍然生效。这种"洋葱模型"确保即使外层被绕过,内层仍然保护关键资源。
// permissions.ts:1158-1319 — 管线关键锚点
async function hasPermissionsToUseToolInner(tool, input, context) {
// 1a. 全局 deny — 最高优先级,不可覆盖
const denyRule = getDenyRuleForTool(context, tool) // L1171
if (denyRule) return { behavior: 'deny' }
// 1g. 安全路径 — bypass-immune
if (toolPermissionResult?.behavior === 'ask'
&& toolPermissionResult.decisionReason?.type === 'safetyCheck') {
return toolPermissionResult // 即使 bypass 模式也必须提示
}
// 2a. bypass 模式 — 在安全检查之后
if (shouldBypassPermissions) return { behavior: 'allow' }
// 3. 无匹配规则 → ask(fail-closed)
return toolPermissionResult.behavior === 'passthrough'
? { ...toolPermissionResult, behavior: 'ask' }
: toolPermissionResult
}
--dangerously-skip-permissions 修改 .git/config 执行恶意代码。当前顺序保证了安全检查的不可绕过性——这是代码注释中反复强调"bypass-immune"的原因。2. Auto 模式分类器 — 渐进式自动化
Auto 模式不是简单的"自动放行",而是一个多层次的决策系统。在调用昂贵的 AI 分类器之前,先检查三个快速路径:acceptEdits 模拟(permissions.ts:600-656,本地检查零延迟)、安全工具白名单(L660,预定义列表)和连续拒绝计数器(防止分类器被反复滥用)。只有快速路径都未命中时,才调用分类器。
// permissions.ts:600-686 — Auto 模式决策层级
// Tier 1: acceptEdits 模拟(本地计算,零延迟)
if (tool.name !== AGENT_TOOL_NAME && tool.name !== REPL_TOOL_NAME) {
const acceptEditsResult = await tool.checkPermissions(parsedInput, {
...context,
getAppState: () => ({
...state,
toolPermissionContext: { ...state.toolPermissionContext, mode: 'acceptEdits' }
}),
})
if (acceptEditsResult.behavior === 'allow') return allow // 快速放行
}
// Tier 2: 安全工具白名单(预定义列表,零 API 调用)
if (isAutoModeAllowlistedTool(tool.name)) return allow
// Tier 3: AI 分类器(Haiku 调用,~1s 延迟)
const classifierResult = await classifyYoloAction(messages, action, ...)
3. 连续拒绝追踪 — 自适应安全升级
permissions.ts:483-501 和 denialTracking.ts 实现了连续拒绝计数器。在 auto 模式下,如果分类器连续拒绝多个工具调用(表示模型可能在尝试危险操作链),系统会升级到用户确认模式。一次成功的工具执行(L486-501)就重置计数器,恢复正常自动模式。
// permissions.ts:483-501 — 成功执行重置计数器
if (result.behavior === 'allow') {
const currentDenialState =
context.localDenialTracking ?? appState.denialTracking
if (appState.toolPermissionContext.mode === 'auto'
&& currentDenialState
&& currentDenialState.consecutiveDenials > 0) {
const newDenialState = recordSuccess(currentDenialState)
persistDenialState(context, newDenialState)
}
return result
}
4. checkRuleBasedPermissions — Hook 专用快速路径
permissions.ts:1071-1156 的 checkRuleBasedPermissions() 是权限管线的子集,只执行步骤 1a-1g 的规则匹配部分,不涉及模式判断(bypass/auto/dontAsk)和 AI 分类器。这个快速路径供 PreToolUse hooks 使用——hooks 需要知道工具是否被规则允许或拒绝,但不应触发完整的模式逻辑和分类器调用。
// permissions.ts:1071-1076
export async function checkRuleBasedPermissions(
tool: Tool, input, context
): Promise {
// 只执行步骤 1a-1g,不涉及模式/分类器
// 1a. 全局 deny
// 1b. 全局 ask
// 1c. 工具自身 checkPermissions
// 1d-1g. 安全免疫检查
return null // 无规则反对 → 返回 null(交给调用方决定)
}
◈ Agent 实践借鉴 — 垂直 Agent 权限设计
一、场景映射:客服 Agent 遇到的权限问题
你的客服 agent 有这些工具:查询订单、查询物流、修改地址、退单/退款、发放优惠券、修改会员等级、查看客户隐私信息。不同坐席有不同权限:初级客服只能查询,高级客服可以退单,主管可以改会员等级。你需要回答:
- agent 自动执行退单时,要不要人审批?退 50 元和退 5000 元策略一样吗?
- agent 被恶意用户引导去执行不该执行的操作(社会工程攻击),怎么防?
- 某些操作(如删除客户数据)无论什么角色都不能自动执行,怎么保证?
- 坐席说"帮我自动处理所有退款",你怎么在放权的同时保住安全底线?
二、借鉴 CC:精简版权限管线(5 步而非 13 步)
Claude Code 用了 13 步管线,因为它面对的是通用开发环境——任意工具、任意命令、不可预测的用户意图。你的垂直 agent 场景更收敛,5 步管线足够覆盖:
// 借鉴 CC 的 hasPermissionsToUseToolInner,精简为 5 步
// CC 用 13 步是因为通用场景,客服场景 5 步即可
async function checkPermission(
action: AgentAction, // 如 { tool: 'refund', orderId: 'xxx', amount: 5000 }
context: AgentContext, // 包含坐席角色、当前会话、客户信息
): Promise<PermDecision> {
// Step 1: 全局禁止规则(对应 CC Step 1a)
// 场景:公司规定 VIP 客户不能被某个渠道的 agent 退款
const denyRule = getDenyRule(context.tenant, action.tool);
if (denyRule) return { behavior: 'deny', reason: 'business_rule_deny' };
// Step 2: 角色权限检查(对应 CC Step 1c 工具自身检查)
// 场景:初级坐席不能退单,高级坐席可以
if (!context.role.permissions.includes(action.tool)) {
return { behavior: 'deny', reason: 'role_insufficient' };
}
// Step 3: 操作风险分级(借鉴 CC auto 模式的快速路径思路)
// 场景:退款 < 200 元自动通过,200-2000 需确认,>2000 需主管审批
const riskLevel = evaluateRisk(action, context);
if (riskLevel === 'auto') return { behavior: 'allow', reason: 'low_risk' };
if (riskLevel === 'confirm') return { behavior: 'ask', reason: 'medium_risk' };
if (riskLevel === 'escalate') {
return { behavior: 'ask', reason: 'high_risk',
escalateTo: 'supervisor' }; // 升级到主管
}
// Step 4: ★ 免疫锚点(借鉴 CC bypass-immune 思路)★
// 场景:删除客户数据、修改系统配置——无论角色和风险级别,必须人工确认
const IMMUNE_OPERATIONS = [
'delete_customer', // 删除客户数据
'modify_vip_level', // 修改 VIP 等级(防内部舞弊)
'export_customer_data', // 导出客户隐私数据
'system_config_change', // 系统配置变更
];
if (IMMUNE_OPERATIONS.includes(action.tool)) {
return { behavior: 'ask', reason: 'immune_checkpoint',
bypassImmune: true, // 即使开启了自动模式也必须确认
escalateTo: 'admin' };
}
// Step 5: 默认放行(借鉴 CC 的 allow 白名单)
// 查询类操作、低风险通知类操作直接通过
return { behavior: 'allow', reason: 'default_allow' };
}
三、借鉴 CC:免疫锚点 — 安全的最后一道防线
CC 的 bypass-immune 设计(L1252-1260)是这个权限系统最有价值的模式。核心思想:某些操作的安全检查在"模式路由"之前执行,即使管理员开启了"自动模式"也绕不过去。
--dangerously-skip-permissions 也改不了 .git/config。// 免疫锚点实现 — 借鉴 CC permissions.ts:1252-1260 的思路
// 关键:immune 检查在 auto-allow 之前,顺序保证安全
function evaluateRisk(
action: AgentAction, context: AgentContext
): 'auto' | 'confirm' | 'escalate' {
// ★ 先做免疫检查(在自动放行之前!)
const IMMUNE_OPERATIONS = new Set([
'delete_customer', 'modify_vip_level',
'export_customer_data', 'system_config_change',
]);
if (IMMUNE_OPERATIONS.has(action.tool)) {
// 免疫操作:无论什么角色/模式,都必须人工确认
// 即使坐席说"帮我自动处理所有事情",这条也过不去
return 'escalate'; // 强制升级到管理员确认
}
// 然后按业务规则分级
if (action.tool === 'refund') {
if (action.amount <= 200) return 'auto'; // 低风险:自动通过
if (action.amount <= 2000) return 'confirm'; // 中风险:坐席确认
return 'escalate'; // 高风险:主管审批
}
if (action.tool === 'change_address') return 'auto'; // 低风险
if (action.tool === 'query_order') return 'auto'; // 只读,无风险
return 'confirm'; // 未知操作默认需确认(借鉴 CC 的 fail-closed)
}
// 反面教材:如果把免疫检查放在 auto-allow 之后
// riskLevel === 'auto' → allow → 免疫检查永远执行不到
// 坐席开了"自动模式"后,delete_customer 也会被自动执行
// 这就是 CC 把 1g 放在 2a 之前的原因
四、借鉴 CC:渐进式自动决策(三层快速路径)
CC 的 auto 模式用三层快速路径(acceptEdits 模拟 → 白名单 → AI 分类器)实现 90%+ 自动化。你的客服 agent 不需要 AI 分类器——业务规则是可穷举的,静态规则表比 AI 更可控。但 渐进式决策的分层思路 可以直接复用:
// 借鉴 CC auto 模式的三层快速路径
// CC: acceptEdits → 白名单 → AI分类器
// 客服Agent: 规则表 → 风险评分 → 人工升级(不需要AI分类器)
async function autoModeDecision(
action: AgentAction, context: AgentContext
): Promise<PermDecision> {
// Tier 1: 静态规则表(对应 CC 的白名单,0ms)
// 客服场景的操作是可穷举的,静态规则比 AI 更可靠
const STATIC_RULES: Record<string, AutoRule> = {
'query_order': { risk: 0, auto: true }, // 纯查询
'query_logistics': { risk: 0, auto: true }, // 纯查询
'change_address': { risk: 10, auto: true }, // 低风险修改
'send_coupon': { risk: 20, auto: true, limit: 50 }, // 有额度限制
'refund': { risk: 50, auto: false }, // 需要评估
'modify_vip': { risk: 100, auto: false }, // 免疫操作
};
const rule = STATIC_RULES[action.tool];
if (rule?.auto) {
// 额外检查额度限制(如每日发券上限)
if (rule.limit && context.dailyUsage[action.tool] >= rule.limit) {
return { behavior: 'ask', reason: 'daily_limit_exceeded' };
}
return { behavior: 'allow', reason: 'static_rule_auto' };
}
// Tier 2: 风险评分引擎(对应 CC 的 acceptEdits 模拟)
// 综合考虑:操作类型 × 金额 × 客户等级 × 坐席等级 × 当日累计
const score = calculateRiskScore(action, context);
if (score < 30) return { behavior: 'allow', reason: 'low_risk_score' };
if (score < 70) return { behavior: 'ask', reason: 'medium_risk_score' };
return { behavior: 'ask', reason: 'high_risk_score', escalateTo: 'supervisor' };
// 注意:不需要 Tier 3(AI 分类器)
// CC 需要 AI 分类器是因为它面对不可预测的操作(任意 bash 命令)
// 客服场景的操作类型是有限的,静态规则 + 风险评分足够
}
function calculateRiskScore(action: AgentAction, ctx: AgentContext): number {
let score = 0;
// 因子 1:金额(退款 5000 元比退款 50 元风险高)
score += Math.min(action.amount / 100, 50);
// 因子 2:当日累计(已经退了 5 单了,第 6 单要提高警惕)
const dailyTotal = ctx.dailyRefundTotal + (action.amount ?? 0);
if (dailyTotal > 10000) score += 30;
// 因子 3:客户等级(VIP 客户的操作需要更谨慎)
if (ctx.customer.vipLevel >= 3) score += 15;
// 因子 4:坐席与客户关联(异常关联可能暗示内部舞弊)
if (ctx.hasAnomalySignal) score += 20;
return Math.min(score, 100);
}
五、借鉴 CC:连续拒绝追踪与自动升级
CC 用连续拒绝计数器防止 auto 模式被提示注入攻击绕过(L878-901)。客服场景同样需要:当 agent 在短时间内被连续要求执行高风险操作时,自动升级到人工确认,防止社会工程攻击或 agent 被误导。
// 借鉴 CC denialTracking.ts 的连续拒绝追踪
// CC: 连续拒绝 3 次 → 升级到用户确认
// 客服Agent: 连续被要求执行高风险操作 → 自动升级到主管
interface SessionRiskTracking {
consecutiveHighRiskRequests: number; // 连续高风险请求数
totalActions: number; // 本次会话总操作数
riskScoreSum: number; // 累计风险分
startTime: number;
}
const ESCALATION_THRESHOLD = 3; // 连续 3 次高风险请求 → 升级
function checkAndRecord(
action: AgentAction,
tracking: SessionRiskTracking,
decision: PermDecision
): PermDecision {
if (decision.behavior === 'allow') {
// 成功执行 → 重置连续计数(借鉴 CC recordSuccess)
return decision;
}
// 被拒绝或需要确认 → 记录
tracking.consecutiveHighRiskRequests++;
if (tracking.consecutiveHighRiskRequests >= ESCALATION_THRESHOLD) {
// 连续 3 次高风险 → 切换到"谨慎模式"
// 接下来所有操作都需要坐席确认,不再自动放行
return {
behavior: 'ask',
reason: 'consecutive_risk_escalation',
message: '检测到连续高风险操作请求,已自动升级为人工确认模式',
};
}
return decision;
}
// 场景:恶意用户不断要求"帮我退款""换个地址退款""再退一次"
// 每次单独看金额都不高,但连续模式暴露异常意图
// 这正是 CC denial tracking 设计要防御的模式
六、落地清单:从 CC 到你的 Agent
- 免疫锚点:定义 3-5 个无论什么角色/模式都必须二次确认的操作,检查放在自动放行之前。这是你权限系统安全的底线。
- Fail-closed 默认:未知操作默认 deny/ask,不是 allow。CC 的管线末尾是 ask,你的也应该是。
- 渐进式决策:从便宜到昂贵。能用静态规则就不算风险分,能算分就不叫人。但免疫操作例外。
- 不需要 13 步管线:CC 面对任意命令需要多层过滤,你的操作类型是有限的,5 步足够。
- 不需要 AI 分类器:CC 用 Haiku 做 YOLO 判断是因为 bash 命令不可穷举。客服操作是可枚举的,静态规则表 + 风险评分更可控、更快、更便宜。
- 不需要 bypass 模式:CC 有
--dangerously-skip-permissions是因为开发者有时要快速试错。客服场景不应该有"跳过所有权限"的开关。 - 不需要 dontAsk 模式:CC 在非交互模式下把 ask 转 deny。你的 agent 应该始终有交互通道(坐席/主管)。
- 免疫检查放在自动放行之后:最常见的致命错误。如果
if (mode === 'auto') return allow在免疫检查之前执行,那么开启自动模式后删客户数据也会被自动执行。顺序决定安全。 - 风险评分是静态的:退款 5000 元对大客户是日常操作,对小客户是异常。风险评分必须结合上下文(客户等级、历史行为、当日累计),不能只看金额。
- 没有连续行为检测:单次退款 200 元自动通过没问题,但连续退 20 笔 200 元就是异常。CC 的 denial tracking 就是为了捕捉这种"单次看正常、连续看异常"的模式。
代码索引
| 文件 | 行数 | 说明 |
|---|---|---|
types/permissions.ts | ~442 | 所有权限类型定义:PermissionMode, PermissionDecision, PermissionRule, ToolPermissionContext 等 |
utils/permissions/permissions.ts | ~1487 | 权限检查核心:hasPermissionsToUseTool (13步), checkRuleBasedPermissions, getDenyRuleForTool 等 |
utils/permissions/permissionSetup.ts | ~1533 | 权限模式初始化、转换、危险权限剥离、auto 模式门控 |
utils/permissions/PermissionMode.ts | ~142 | PermissionMode 配置、标题、符号、颜色映射 |
utils/permissions/PermissionResult.ts | ~36 | 权限结果类型和帮助函数(re-export) |
utils/permissions/bashClassifier.ts | ~62 | Bash 命令分类器(外部版为空壳) |
utils/permissions/denialTracking.ts | ~文件 | 连续拒绝追踪状态管理 |
utils/permissions/yoloClassifier.ts | ~文件 | YOLO 模式分类器 |
utils/permissions/dangerousPatterns.ts | ~文件 | 危险命令模式检测 |
utils/permissions/pathValidation.ts | ~文件 | 路径安全验证(.git/ 等保护路径) |
utils/permissions/shellRuleMatching.ts | ~文件 | Shell 命令规则匹配逻辑 |
utils/permissions/permissionRuleParser.ts | ~文件 | 权限规则字符串解析器 |
utils/permissions/filesystem.ts | ~文件 | 文件系统权限检查 |
hooks/toolPermission/PermissionContext.ts | ~389 | 交互式权限上下文:Hook 运行、分类器、队列操作 |
hooks/toolPermission/handlers/interactiveHandler.ts | ~文件 | 主 agent 交互式权限处理 |
hooks/toolPermission/handlers/coordinatorHandler.ts | ~文件 | 协调器工作线程权限处理 |
hooks/toolPermission/handlers/swarmWorkerHandler.ts | ~文件 | Swarm 工作线程权限处理 |
utils/sandbox/sandbox-adapter.ts | ~986 | 沙箱适配层:配置转换、初始化、命令包装、平台检测 |
utils/sandbox/sandbox-ui-utils.ts | ~文件 | 沙箱相关 UI 工具函数 |