安全设计
本文总结仓库中“安全层”的实际实现方式。这里的“安全层”不是单独一个模块,而是一套横切系统,覆盖:
- 工具是否对模型可见
- 工具调用前是否允许执行
- 文件/命令/远端桥接是否触达敏感边界
- 自动模式是否会绕过人工确认
- 共享记忆是否会把秘密同步出去
核心代码主要分布在:
src/utils/permissions/*src/services/tools/*src/tools.tssrc/utils/permissions/filesystem.tssrc/utils/permissions/yoloClassifier.tssrc/bridge/*src/services/teamMemorySync/*
1. 总体设计思路
这套安全设计不是“工具内部各写一套校验”,而是平台统一裁决,分成几层:
- 能力暴露层 在模型看到工具列表之前,先按 deny rule 过滤工具,直接缩小能力边界。
- 规则判定层
用统一的
ToolPermissionContext、allow/deny/ask 规则和模式状态做首轮裁决。 - 工具专属安全层
每个工具在
checkPermissions()里补充自己的内容级安全检查,例如文件路径、shell 子命令、sandbox 相关判断。 - 自动化安全层 auto mode 下再接一层分类器,避免“规则允许但语义危险”的动作直接落地。
- 交互审批层 无法自动放行时,进入交互式确认、远端桥接确认,或 headless 下拒绝。
- 执行后治理层 Pre/Post hook、拒绝 hook、审计日志、拒绝计数、提示建议,形成闭环。
换句话说,它不是“只有执行前问一次用户”,而是“先缩工具池,再做规则判定,再做语义安全,再决定是否要问用户”。
2. 核心数据结构
2.1 ToolPermissionContext
定义见:
src/Tool.tssrc/types/permissions.ts
它是整套权限系统的运行时上下文,核心字段包括:
mode当前权限模式,如default、acceptEdits、plan、bypassPermissions、auto。alwaysAllowRules/alwaysDenyRules/alwaysAskRules按来源拆分的规则集合。additionalWorkingDirectories额外加入权限范围的工作目录。isBypassPermissionsModeAvailable是否允许进入 bypass 模式。isAutoModeAvailableauto mode 是否可用。shouldAvoidPermissionPrompts当前上下文是否不能弹权限框,例如后台 agent。awaitAutomatedChecksBeforeDialog是否在显示权限框前先等自动检查完成。strippedDangerousRules进入 auto mode 时临时剥离的危险 allow 规则。
2.2 规则模型
定义见:
src/types/permissions.tssrc/utils/permissions/permissionRuleParser.ts
规则由三部分组成:
source来源,例如userSettings、projectSettings、localSettings、cliArg、session、policySettings。ruleBehaviorallow/deny/askruleValue形如toolName + ruleContent
规则字符串支持:
- 整个工具级别规则,如
Bash - 带内容的规则,如
Bash(npm publish:*) - 转义括号内容
- legacy tool 名映射,避免工具改名后旧规则失效
3. 第一层:先过滤“模型看得到什么”
实现见:
src/tools.tssrc/utils/permissions/permissions.ts
关键点:
filterToolsByDenyRules()会在工具暴露给模型前,直接删除被 deny rule 命中的工具。- 这不仅作用于内建工具,也作用于 MCP 工具。
- 对 MCP 还支持 server 级别屏蔽,例如
mcp__server或mcp__server__*。
这意味着安全边界并不只在“调用时”判断,而是更早在“能力暴露阶段”就生效。模型从一开始就看不到明确被禁用的能力。
4. 第二层:统一权限决策链
主流程在:
src/services/tools/toolExecution.tssrc/utils/permissions/permissions.tssrc/hooks/useCanUseTool.tsx
4.1 调用顺序
一次工具调用的大致顺序是:
- 解析输入并做
backfillObservableInput - 执行
PreToolUsehooks - 合并 hook 给出的
updatedInput/permissionResult - 调
hasPermissionsToUseTool() - 若需要,进入 auto classifier 或交互审批
- 允许后再真正执行工具
- 执行
PostToolUse/PostToolUseFailurehooks
4.2 hasPermissionsToUseToolInner() 的裁决顺序
关键顺序很清晰:
- 整个工具 deny rule
- 整个工具 ask rule
- 工具自己的
checkPermissions() - 工具级 deny 结果
- 必须用户交互的工具保持 ask
- 内容级 ask rule 保持 ask
safetyCheck保持 ask- 若是
bypassPermissions,且未命中前面的强约束,则 allow - 若整个工具有 allow rule,则 allow
- 剩余的
passthrough统一转成ask
这套顺序说明两件事:
deny、ask、safetyCheck的优先级高于 bypass 模式。- allow rule 不是无条件生效,它排在工具专属安全检查之后。
5. 第三层:文件系统安全
实现主要在:
src/utils/permissions/filesystem.tssrc/utils/permissions/pathValidation.tssrc/tools/FileReadTool/FileReadTool.tssrc/tools/FileEditTool/FileEditTool.tssrc/tools/FileWriteTool/FileWriteTool.ts
5.1 设计原则
文件权限不是简单看“是不是在 cwd 下”,而是同时考虑:
- 工作目录与附加目录
- symlink 解析后的真实路径
- read/edit 独立规则
acceptEdits模式- sandbox 写白名单
- Claude 自身配置目录与敏感目录
- Windows/UNC 特殊路径绕过
- 内部运行目录的受控豁免
5.2 路径检查顺序
写权限 checkWritePermissionForTool() 的顺序大致是:
- edit deny rule
- 内部可写路径豁免
.claude/**的 session 级特殊 allow 规则checkPathSafetyForAutoEdit()- edit ask rule
acceptEdits + 工作目录内自动允许- edit allow rule
- 默认 ask
读权限 checkReadPermissionForTool() 则是:
- UNC 与可疑 Windows 路径拦截
- read deny rule
- read ask rule
- “有 edit 权限则可读”
- 工作目录内可读
- 内部可读路径豁免
- read allow rule
- 默认 ask
5.3 敏感路径与绕过防护
checkPathSafetyForAutoEdit() 会拒绝自动编辑这些对象:
.git、.vscode、.idea、.claude.gitconfig、.gitmodules、shell rc 文件等- Claude 自己的 settings / commands / agents / skills 目录
- 可疑 Windows 路径模式 例如 ADS、8.3 短文件名、长路径前缀、尾随点/空格、设备名、UNC
而且它不是只检查原路径,还会检查 getPathsForPermissionCheck() 得到的解析路径,避免通过 symlink 绕过。
5.4 工作目录模型
工作目录范围由:
- 原始 cwd
additionalWorkingDirectories
共同决定。
pathInAllowedWorkingPath() 会对输入路径和工作目录都做对称解析,再判断是否位于允许范围内。这解决了:
- symlink cwd
- macOS
/tmp//private/tmp - 大小写不敏感文件系统
带来的假阴性或绕过问题。
5.5 内部路径豁免
为了让系统自身可运行,代码显式放行一批“受控内部路径”,例如:
- 当前 session 的 plan 文件
- scratchpad
- session memory
- tool results
- project temp 目录
- agent memory / auto memory
- tasks / teams / bundled skills 提取目录
这些豁免都是明确列举的,不是“凡是 .claude 下都放行”。
6. 第四层:Shell 与命令安全
实现见:
src/utils/permissions/shellRuleMatching.tssrc/utils/permissions/dangerousPatterns.tssrc/utils/permissions/permissionSetup.tssrc/utils/permissions/permissions.ts
6.1 规则匹配能力
shell 规则支持三类匹配:
- exact
- legacy prefix,如
npm:* - wildcard,如
git *
这让“命令级 allow/ask/deny”可以细到具体子命令,而不是只能对整个 Bash/PowerShell 开大口子。
6.2 auto mode 对危险 allow 规则做“去武器化”
进入 auto mode 时,stripDangerousPermissionsForAutoMode() 会主动剥离危险 allow 规则,防止它们绕过分类器。
典型危险规则包括:
Bash(*)Bash(python:*)Bash(node:*)PowerShell(*)PowerShell(iex:*)PowerShell(Start-Process:*)- 任意
Agent(...)allow
离开 auto mode 时,再通过 restoreDangerousPermissions() 恢复。
这说明 auto mode 不是简单“把默认模式改个名字”,而是重新收紧了一次权限上下文。
6.3 auto classifier 的位置
实现见:
src/utils/permissions/yoloClassifier.tssrc/utils/permissions/classifierDecision.tssrc/utils/permissions/denialTracking.ts
auto mode 的判定大致分三层:
- 快速放行路径
- 如果在
acceptEdits语义下本来就会允许,则不走分类器 - 某些安全工具在 allowlist 中,直接放行
- 如果在
- 分类器判定
classifyYoloAction()对动作和上下文做语义安全判断- XML classifier 支持两阶段模式:先 fast,再在需要时进入 thinking
- 失败保护
- transcript 太长时回退到手动审批
- 分类器不可用时可 fail closed 或 fail open,受 gate 控制
- 连续拒绝和总拒绝次数过高时,退回人工审阅
因此 auto mode 的本质是“规则层 + 语义分类层”的叠加,不是彻底自动放行。
6.4 sandbox 也是权限链的一部分
Bash ask rule 在特定条件下可被 sandbox 自动放行逻辑覆盖:
- sandbox 已启用
- 配置允许 “sandboxed bash auto allow”
- 当前命令确实会在 sandbox 中执行
也就是说,sandbox 不是独立附加功能,而是会直接参与权限决策。
7. 第五层:交互审批、Hook 与 headless 约束
实现见:
src/hooks/useCanUseTool.tsxsrc/hooks/toolPermission/handlers/interactiveHandler.tssrc/services/tools/toolHooks.tssrc/utils/permissions/permissions.ts
7.1 交互审批
当结果是 ask 时:
- 主线程会进入交互式权限框
- 可以被 classifier/hook 在后台抢先放行
- 也可以转发给 bridge/channel 做远端确认
7.2 headless / 后台 agent
如果 shouldAvoidPermissionPrompts = true,系统不会假装“等用户确认”,而是:
- 先给
PermissionRequesthook 一次机会 - 如果 hook 没有明确 allow/deny
- 直接 auto deny
这避免了后台 agent 卡死在无 UI 的审批点。
7.3 Hook 位置
Hook 不是可有可无的扩展,而是嵌在安全链里:
PreToolUse可以修改输入、阻断执行、返回权限结果PermissionRequest可以替代用户审批PermissionDenied可以在 auto deny 后决定是否允许重试PostToolUse/PostToolUseFailure做后处理与治理
这使得权限系统是“平台统一规则 + 可插拔策略”的组合。
8. 第六层:远端桥接与鉴权
实现见:
src/bridge/jwtUtils.tssrc/bridge/trustedDevice.tssrc/bridge/workSecret.tssrc/bridge/bridgePermissionCallbacks.tssrc/remote/remotePermissionBridge.ts
8.1 远端权限请求不会绕过本地审批模型
远端会话需要审批时,bridge 会把请求转换成统一的权限请求结构,再由本地同一套 ToolUseConfirm 流程处理。对于本地不认识的远端工具,还会创建最小 stub,走 fallback permission request。
因此远端工具不是另开一条“弱化版审批通道”,而是尽量复用本地审批体系。
8.2 会话令牌与刷新
createTokenRefreshScheduler() 会根据 JWT exp 提前刷新 session token,避免长会话过期失效。它带有:
- refresh buffer
- 失败重试
- generation 防抖,避免旧刷新任务污染新会话
8.3 trusted device
bridge 的 elevated 会话还引入 trusted device token:
- token 存在 secure storage
- 受 gate 控制是否启用
/login后尽早 enrollment- 支持清缓存和账号切换场景
这相当于对 OAuth 之外再补一层“设备信任”。
8.4 work secret 与 session 绑定
decodeWorkSecret() 会校验版本和必要字段;
sameSessionId() 会在不同 tagged id 之间比较底层 session body,防止桥接过程把别的 session 误当成当前 session。
9. 第七层:共享记忆的秘密防泄漏
实现见:
src/services/teamMemorySync/secretScanner.tssrc/services/teamMemorySync/teamMemSecretGuard.tssrc/tools/FileWriteTool/FileWriteTool.tssrc/tools/FileEditTool/FileEditTool.ts
设计要点:
- 在写入 team memory 前,先在本地扫描内容
- 使用高置信度 secret 规则子集
- 命中后直接拒绝写入
- 扫描发生在上传前,避免秘密离开本机
team memory sync 本身还要求:
- first-party OAuth
- 指定 scope
- repo 级作用域
- delta upload / checksum 同步
所以这里既有“共享数据面”的权限约束,也有“内容面”的秘密扫描。
10. 这套安全层的几个关键特点
10.1 不是单点校验,而是分层收敛
同一个危险动作通常会经过多层约束:
- 工具暴露过滤
- 规则 deny/ask
- 工具专属安全检查
- auto classifier
- 交互审批或 headless 拒绝
10.2 默认保守,但允许局部放宽
系统允许:
- 按工具授权
- 按命令前缀授权
- 按目录授权
- 按 session 临时授权
但又尽量避免“一把梭”授权在 auto mode 下失控,所以会剥离危险 allow 规则、限制 .claude、限制敏感路径、限制 headless prompt。
10.3 明确区分“平台内部路径”和“用户项目路径”
很多内部文件被显式豁免,是为了保证 agent runtime 本身能工作;但这些豁免是精确列举,不会泛化成对整个配置目录的长期开放。
10.4 兼顾安全与可用性
典型例子有:
- sandbox 白名单参与写权限自动放行
- read 权限可从 edit 权限推导
- 给权限对话框生成 suggestions
- auto mode 中对安全工具走 allowlist,减少分类器成本
这说明它并不是“安全优先到不可用”,而是在严格边界内做效率优化。
11. 结论
这个仓库的安全层本质上是一个“平台级权限与安全编排系统”,而不是零散的工具内校验。它的核心设计可以概括为:
- 用统一的
ToolPermissionContext和规则体系收拢权限状态 - 在工具暴露前先裁剪能力边界
- 在执行前按规则、路径、命令、模式做多阶段检查
- 在 auto mode 下额外引入语义分类器,并主动剥离会绕过分类器的危险 allow 规则
- 对远端桥接、共享记忆、敏感路径、Windows/UNC 特殊路径做专项防护
- 用 Hook、建议、审计、拒绝计数把安全机制接入完整执行链路
因此,这套安全设计的重点不是“绝对禁止”,而是把“可授权的范围、不可绕过的边界、自动化的上限”清楚分层,并且把这些层真正落实到了工具池、权限判定、文件系统、自动模式、远端桥接和数据同步的代码里。