权限系统,规则、模式与分类器
模型想执行一个工具,最后一道闸门在 src/utils/permissions/permissions.ts,入口函数 hasPermissionsToUseTool。它分两层,内层 hasPermissionsToUseToolInner 跑一条有序决策链,外层对内层结论做模式转换。函数签名上的返回值有三种行为,allow 放行,deny 拒绝,ask 转交用户裁决。整个系统的复杂度都在这条链和这几个转换上。
决策链的顺序有讲究,源码里用编号注释标得清清楚楚。第一步查规则,用户在配置里写的 deny 规则命中整工具,直接拒绝,没有商量。ask 规则命中则要询问,唯一的例外是 Bash 配了沙箱自动放行时往下放行,交给 Bash 自己的检查。第二步让工具自查,调用上一章说的 checkPermissions,内容级的判断在这里,某个路径能不能改,某条命令能不能跑。自查的结论分几个出口,内容级 deny 直接拒绝。两个特殊出口越过后面一切模式,内容级 ask 规则和安全检查,改 .git、.claude、.vscode 这类敏感路径触发的询问连 bypass 模式都拦不住,源码注释称之为 bypass 免疫。第三步看模式,bypassPermissions 直接放行。第四步查 allow 规则,命中放行。全都走完还没结论,默认 ask。
模式一共六种,default、plan、acceptEdits、bypassPermissions、dontAsk、auto,终端里按 shift 加 tab 循环切换,切换逻辑在 getNextPermissionMode。模式对链上结论做二次加工,外层函数在链返回后接手。dontAsk 把 ask 转成 deny,用户不想被问,那就一律拒绝。bypassPermissions 在链中段就放行了,但如前所述,安全检查和内容级 ask 规则拦在它前面,用户显式配置的约束优先级高于模式,这是整条链最重要的优先级安排。
auto 模式是这份源码里最值得细读的设计,界面上俗称 YOLO。它接手每一个 ask 决策,先走快路径,安全工具白名单直接放行,acceptEdits 范围内的编辑重放结论。快路径都 miss 才请出分类器,一个用 sideQuery 跑的旁路模型调用,不进对话历史,专门裁决这次工具调用危不危险,代码在 yoloClassifier.ts,提示词单独放在一个目录里维护。auto 和 bypass 的差别就在这里,bypass 是无条件信任,auto 是每一次询问都过一道机器审查,审错了还有熔断兜底。
熔断机制叫 denialTracking,统计连续拒绝次数。分类器连续拒绝达到阈值,就不再自动裁决,退回真人询问,避免模型在一条死路上无限空转。任何一次放行都会清零计数,源码特意注释了这个设计,让规则放行的成功调用也能打断连续拒绝的记录。计数状态对异步子代理有单独的本地副本,因为异步子代理改不动全局状态,注释里说明了不这样做阈值永远到不了。
规则本身的内容级匹配也值得一提。Bash 的规则可以写命令级别的通配,git 空格星号表示放行所有 git 子命令,匹配实现在 shellRuleMatching,依赖每个工具提供的匹配器准备函数把规则编译成断言。配置的来源和写法在这份仓库配套的权限配置笔记里有完整整理,六种配置方式按优先级叠在这个决策链的入口处。理解了链、模式、分类器三层,权限系统的任何行为都能对上号。