YOLO 分类器与路径安全:当 AI 自己判断风险等级

发布时间:2026/6/30 4:06:04
YOLO 分类器与路径安全:当 AI 自己判断风险等级 YOLO 分类器与路径安全当 AI 自己判断风险等级《Claude Code 架构解密》精读笔记 · 第07篇覆盖章节第5章中段5.5-5.7| 页码p.119-127导语上一篇我们拆解了 Claude Code 权限系统的骨架——14步决策树和8层规则优先级。但一个骨架再完整也只能处理已知模式规则匹配的是你写过的命令、定义过的路径。当一个从未见过的 Bash 命令出现时规则引擎只能默默走默认路径——ask弹确认框。在 Auto 模式下这个默认 ask就成了一道效率枷锁。Claude Code 的解法不是放宽规则而是引入了一位 AI 安全员——YOLO Classifier。它用 LLM 的语义理解能力判断这个操作危险吗在规则引擎的盲区铺设了一层智能防线。同时规则引擎本身也不简单。Shell 通配符匹配、gitignore 风格的路径规则、Windows 路径绕过检测——这些看似基础设施的代码恰恰是权限安全的底层基石。本篇我们将深入规则匹配引擎——三种 Shell 命令匹配 四种路径前缀语义 六种 Windows 绕过检测YOLO Classifier——两阶段快慢路径设计的精妙之处断路器模式——当 AI 安全员卡死时的兜底机制一、规则匹配引擎Shell 通配符与路径安全1.1 Shell 命令规则的三种类型权限规则匹配的核心引擎位于shellRuleMatching.ts支持三种粒度typeShellPermissionRule|{type:exact;command:string}// 精确匹配: npm install|{type:prefix;prefix:string}// 前缀匹配: npm:*|{type:wildcard;pattern:string}// 通配符匹配: git commit -m *三种类型的设计逻辑类型粒度典型场景安全直觉exact最细npm install“这个特定命令我见过可以放行”prefix中等npm:*“所有 npm 子命令都安全”wildcard最粗git commit -m *“任意 commit 消息都行”冒号语法npm:*是 Claude Code 独有的简写本质是前缀匹配的语法糖——比通配符更安全因为它只能匹配主命令 子命令的结构不会跨命令边界。1.2 通配符匹配算法六步管道通配符匹配是规则引擎中最精巧的部分。以git commit -m *匹配git commit -m fix bug为例跟踪完整的六步管道输入: pattern git commit -m *, command git commit -m fix bug 步骤1: 处理转义序列 \* → \x00占位符防止被步骤3误处理 \! → \x01占位符 步骤2: 转义正则特殊字符 . ? $ ( ) [ ] 全部加上 \ 步骤3: 未转义的 * → .* → git commit \\-m .* 步骤4: 单通配符尾部可选化 如果模式以 .* 结尾且只有一个通配符 → git commit \\-m (.*)? 步骤5: 占位符还原为转义字面量 步骤6: 编译为 RegExp启用 s 标志dotAll两个巧妙之处① \x00 占位符技巧。用户可能写\*表示匹配字面量星号而非通配符。如果不先将\*替换为占位符步骤3会错误地将其转换为\.*。使用 NUL 字符\x00作为占位符几乎不会与真实输入冲突——这是处理转义的转义问题的经典技巧。② 尾部可选化。git *被转换为git (.*)?既匹配git无参数又匹配git add有参数。这让规则的行为符合用户直觉——允许所有 git 命令当然也应该允许不带参数的git。sdotAll标志让.能匹配换行符从而支持包含 heredoc 的多行命令。1.3 文件路径规则gitignore 风格匹配对于文件操作工具FileEdit、FileWrite、FileRead 等路径规则使用ignore库实现 gitignore 风格的模式匹配functionmatchingRuleForInput(path,context,toolType,behavior){constpatternsByRootgetPatternsByRoot(context,toolType,behavior)for(const[root,patternMap]ofpatternsByRoot){// 移除 /** 后缀ignore 库自动匹配子目录constpatterns[...patternMap.keys()].map(pp.endsWith(/**)?p.slice(0,-3):p)constigignore().add(patterns)constrelativePathrelative(root??getCwd(),absolutePath)if(ig.test(relativePath).ignored)returnpatternMap.get(matchedPattern)}returnnull}四种路径前缀语义覆盖从绝对路径到相对路径的所有场景前缀语义示例//path相对于文件系统根目录//etc/passwd~/path相对于用户主目录~/.ssh/config/path相对于设置文件所在的根目录/src/**path无前缀匹配任意位置*.env1.4 Windows 路径绕过检测六种攻击向量文件路径安全不只是路径是否匹配的问题——还要防御利用操作系统特性的路径绕过攻击。hasSuspiciousWindowsPathPattern函数检测 6 种 Windows 特有的绕过手段functionhasSuspiciousWindowsPathPattern(path:string):boolean{// 1. NTFS 备用数据流file.txt::$DATAif(path.indexOf(:,2)!-1)returntrue// 2. 8.3 短名GIT~1 → .gitif(/~\d/.test(path))returntrue// 3. 长路径前缀\\?\C:\..if(path.startsWith(\\\\?\\))returntrue// 4. 尾部点/空格.git. → .gitif(/[.\s]$/.test(path))returntrue// 5. DOS 设备名.git.CONif(/\.(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/i.test(path))returntrue// 6. 三连点.../ → ../if(/(^|\/|\\)\.{3,}(\/|\\|\s)/.test(path))returntrue}攻击向量原理绕过效果NTFS 备用数据流file.txt::$DATA被某些程序解析为file.txt.git::$DATA绕过.git检查8.3 短名Windows 向后兼容 DOS 的遗留特性GIT~1等价于.git长路径前缀\\?\前缀绕过路径长度限制可能绕过基于前缀的路径检查尾部点/空格Windows 文件系统自动剥除.git.实际指向.gitDOS 设备名Windows 保留的设备名.git.CON可能被解析为.git三连点某些文件系统实现处理为上级目录.../等价于../这些检测是跨平台安全思维的典型体现——即使 Claude Code 主要在 Unix 环境下开发也必须防御 Windows 文件系统语义的攻击。1.5 危险路径的硬编码保护除了通用检测权限系统还硬编码了一批免疫 bypass的保护路径constDANGEROUS_FILES[.gitconfig,.gitmodules,// Git 配置.bashrc,.bash_profile,.zshrc,// Shell 配置可注入代码.profile,.zprofile,// 登录配置.ripgreprc,// 搜索工具配置.mcp.json,.claude.json,// Claude/MCP 配置]constDANGEROUS_DIRECTORIES[.git,.vscode,.idea,.claude]“免疫 bypass”意味着即使用户选择了完全信任模式bypassPermissions修改这些文件仍然需要确认。原因很简单这些文件是开发环境的根基——如果 Agent 能不经确认地修改.bashrc它就能在 Shell 启动时注入任意代码如果能修改.claude/settings.json它就能关闭自己的权限限制。设计哲学安全检查的优先级高于用户偏好。这不是限制用户而是保护用户不被 AI 误伤。二、YOLO Classifier两阶段 AI 安全员2.1 为什么需要 AI 分类器规则引擎的根本局限只能匹配已知模式。用户不可能为每种可能的 Bash 命令都预定义规则。当一个命令既没有 deny 规则拦截、也没有 allow 规则放行时默认行为是 ask。但在 Auto 模式下目标是尽量减少人工干预。这时就需要 AI 分类器——用 LLM 的语义理解能力来判断一个操作是否安全。Claude Code 将这个分类器命名为YOLO Classifier是的这个名字确实很有个性位于yoloClassifier.ts。它采用了一个精巧的两阶段设计来平衡准确性和延迟。2.2 两阶段架构快路径/慢路径分离classifyYoloActionXml(systemPrompt, transcript, action) ┌─────────────────────────────────────────┐ │ Stage 1 (Fast) │ │ max_tokens: 64 │ │ stop_sequences: [/block] │ │ 后缀提示Err on the side of blocking │ │ │ │ 输出 blockno/block │ │ → 立即允许跳过 Stage 2 │ │ 输出 blockyes... │ │ → 进入 Stage 2 │ │ 解析失败 │ │ → 进入 Stage 2 │ └───────────────┬─────────────────────────┘ │ 不确定时 ▼ ┌─────────────────────────────────────────┐ │ Stage 2 (Thinking) │ │ max_tokens: 4096 │ │ │ │ 输出 thinking.../thinking │ │ ← 深度推理 │ │ 输出 blockyes|no/block │ │ ← 最终决策 │ │ 输出 reason.../reason │ │ ← 阻止原因 │ │ │ │ blockno → 允许 │ │ blockyes → 阻止 原因 │ │ 解析失败 → 阻止默认安全 │ └─────────────────────────────────────────┘ 返回 YoloClassifierResult: shouldBlock, reason, thinking?, stage, usage, durationMsStage 1快速阶段是一个快速否决器。只用 64 个 token遇到/block就停止生成。对于明显安全的操作如ls -la、cat README.md分类器通常几毫秒内输出blockno/block并结束——根本不需要进入 Stage 2。关键设计细节Stage 1 的后缀提示是“Err on the side of blocking”宁可误阻也不能漏放。这意味着当 Stage 1 说不阻止时我们有很高信心操作确实安全但当 Stage 1 说阻止时可能只是不确定——所以需要 Stage 2 的深度推理来做最终判断。Stage 2推理阶段获得 4096 个 token 的预算可以输出thinking标签内的详细推理过程。这让分类器能处理更复杂的场景——例如python -c import json; print(json.dumps({key: value}))需要理解 Python 代码的实际行为才能判断安全性。性能数据两阶段设计是经典的快路径/慢路径优化。大部分操作在 Stage 1 就能判定将分类器的平均延迟降低了 50-70%。2.3 防提示注入设计分类器面临的最大安全风险是提示注入Prompt Injection——如果恶意仓库中的代码能通过对话转录影响分类器的判断攻击者就能让分类器对危险操作放行。Claude Code 的防御策略体现在转录构建逻辑中functionbuildTranscriptEntries(messages){// 只包含用户文本和助手的 tool_use 块// 不包含助手的文本响应returnmessages.flatMap(msg{if(msg.roleassistant){// 只提取 tool_use 块丢弃文本块returnmsg.content.filter(bb.typetool_use)}// ...})}关键点助手的文本响应被故意排除在转录之外。攻击路径通常是这样的恶意仓库包含精心构造的代码注释或 READMEAgent 读取这些内容后将其复述到自己的文本响应中如果分类器的转录包含助手的文本攻击者的注入内容就进入了分类器的上下文通过只保留tool_use块结构化数据排除自由文本Claude Code 大幅缩小了注入攻击的表面积。2.4 工具安全契约toAutoClassifierInput每个工具可以实现toAutoClassifierInput方法来控制自己在分类器输入中的表示classSomeTool{toAutoClassifierInput(input:ToolInput):string{// 返回 表示此操作不需要分类声明安全// 返回非空字符串表示请分类器评估这个内容return}}返回空字符串是一个优雅的契约声明——工具自己宣称我的这个操作本质上是安全的不需要浪费分类器的 token。例如只读工具Glob、Grep、Read可以直接返回跳过分类器检查。这种设计将安全属性的知识放在了最适合的地方——工具自身。权限系统不需要了解每个工具的内部逻辑只需要信任工具的安全声明。当然这种信任是有限度的——工具的toAutoClassifierInput实现会在代码审查中被仔细审视。2.5 安全工具白名单O(1) 快路径在调用分类器之前还有一个更快的快路径——安全工具白名单constSAFE_YOLO_ALLOWLISTED_TOOLSnewSet([// Tier 1: 只读工具绝对安全FILE_READ_TOOL_NAME,GREP_TOOL_NAME,GLOB_TOOL_NAME,LSP_TOOL_NAME,// Tier 2: 内部状态工具无副作用TODO_WRITE_TOOL_NAME,TASK_CREATE_TOOL_NAME,// Tier 3: 交互工具ASK_USER_QUESTION_TOOL_NAME,ENTER_PLAN_MODE_TOOL_NAME,// 条件工具使用展开运算符优雅处理 FeatureFlag...(WORKFLOW_TOOL_NAME?[WORKFLOW_TOOL_NAME]:[]),])白名单使用Set数据结构查找复杂度 O(1)。对于白名单中的工具连分类器都不需要调用直接放行。注意文件写入/编辑工具不在白名单中。它们通过另一条快路径acceptEdits处理——在工作目录内的编辑直接允许工作目录外的需要分类器评估。这个区分体现了务实的安全判断项目目录内编辑是 Agent 的正常工作行为风险可控但编辑项目目录外的文件如~/.bashrc可能造成跨项目影响。2.6 XML 解析的防御性设计分类器的输出是 XML 格式解析逻辑中有两个关键的防御措施functionparseXmlBlock(text:string):boolean|null{// 先剥离 thinking 标签内容防止匹配到推理过程中的标签constmatches[...stripThinking(text).matchAll(/block(yes|no)\b/gi)]if(matches.length0)returnnull// 解析失败 → 默认阻止returnmatches[0]![1]!.toLowerCase()yes}防御要点一先剥离thinking内容再解析。分类器在推理过程中可能写出如果这是一个恶意命令我应该输出blockyes/block这样的思考过程。如果不先移除 thinking 标签正则表达式可能匹配到推理中的标签而非最终结论。防御要点二解析失败时默认阻止Fail Closed。如果 XML 格式异常导致解析失败系统将其视为阻止而非允许。这是安全系统的铁律在不确定的情况下选择更安全的选项。三、断路器模式当 AI 安全员卡死3.1 问题分类器卡死AI 分类器虽然强大但它可能进入一种卡死状态在某些上下文下分类器反复将同一类操作判定为危险并拒绝导致 Agent 无法完成任务。例如分类器可能在某个上下文中将所有 npm 命令都判定为危险即使用户的意图是正常的包管理操作。为了防止这种情况权限系统实现了一个断路器模式Circuit Breaker位于denialTracking.tstypeDenialTrackingState{consecutiveDenials:number// 连续拒绝计数totalDenials:number// 会话总拒绝计数}constDENIAL_LIMITS{maxConsecutive:3,// 连续拒绝 3 次 → 回退到人工确认maxTotal:20,// 总拒绝 20 次 → 回退到人工确认}3.2 状态转换双重阈值断路器有两个计数器和三个操作recordSuccess recordDenial recordDenial (重置 cons, 1 deny 2 deny 保留 total) cons: 0→1 cons: 1→2 total: N→N1 total: N1→N2 │ │ ▼ ▼ recordDenial 3 deny cons: 2→3 total: N2→N3 │ ▼ cons ≥ limit! shouldFallbackToPrompting true → FALLBACK TO HUMAN双重阈值的设计精妙之处连续 3 次防止当前上下文卡死——分类器在某个对话上下文中可能产生系统性偏见总计 20 次防止长尾误判累积——即使每次只拒绝一两个操作长时间累积也说明分类器在这个会话中表现不佳recordDenial两个计数器都 1recordSuccess只重置连续计数器总计数保留。3.3 不可变数据模式断路器的状态更新采用不可变数据模式与 React 状态管理兼容functionrecordDenial(state:DenialTrackingState):DenialTrackingState{// 返回新对象不修改原状态return{consecutiveDenials:state.consecutiveDenials1,totalDenials:state.totalDenials1,}}functionrecordSuccess(state:DenialTrackingState):DenialTrackingState{// 优化计数已经为 0 时返回原引用避免无意义的 React 重渲染if(state.consecutiveDenials0)returnstatereturn{consecutiveDenials:0,totalDenials:state.totalDenials,}}recordSuccess中的引用相等性优化是一个细节但很实用的技巧——当连续拒绝计数已经为 0 时返回原对象引用避免触发 React 的重渲染。这种状态不变则不更新的思维在 React 应用中非常重要。横向对比三种安全决策路径维度规则引擎YOLO Classifier断路器决策方式模式匹配LLM 语义理解统计计数确定性100% 确定概率性Fast/Thinking阈值触发延迟O(1)~O(n)Stage1: ~ms; Stage2: ~sO(1)覆盖范围已知模式未知模式异常检测失效模式遗漏未知命令提示注入/系统性偏见无法防止首次误判设计哲学Fail-ClosedErr on blocking统计兜底三者形成了一个纵深防御体系请求 → 白名单(O(1))? → 规则引擎(精确/前缀/通配)? → YOLO Classifier(Fast→Thinking)? → 人工确认 ↓ 放行 ↓ allow/deny ↓ block/allow ↓ 用户决策每一层都只处理上一层无法判定的请求形成漏斗式的逐层过滤。这就是为什么规则引擎的简单不影响系统整体安全性——它的职责是快速处理已知情况将未知情况留给更智能的下游。实战启示启示一快路径/慢路径分离是 AI 系统的必修课YOLO Classifier 的两阶段设计是一个通用模式Fast Path低成本、高置信度的快速决策。大部分请求应该在这里处理。Slow Path高成本、深度推理的复杂决策。只有 Fast Path 不确定时才进入。这个模式不仅适用于安全分类也适用于任何 LLM 辅助决策的场景——内容审核、意图识别、异常检测。关键是 Fast Path 的后缀提示“Err on the side of blocking”——宁可让 Slow Path 多处理一些请求也不能让 Fast Path 漏放危险操作。启示二用 AI 管 AI但要设断路器让 LLM 判断 LLM 的操作是否安全这听起来像让狐狸守鸡窝。但 Claude Code 的设计展示了可行的架构限制分类器的输入排除助手自由文本只保留结构化的 tool_use 块工具自声明安全属性toAutoClassifierInput让工具参与安全决策解析防御剥离 thinking 标签、Fail Closed 默认值断路器兜底3 次连续拒绝或 20 次总拒绝后回退到人工核心原则AI 安全员是加速器而非替代者。它的价值是减少 90% 的人工确认而不是消除 100%。启示三跨平台安全不能只看逻辑正确Windows 路径绕过检测揭示了安全工程的一个隐蔽维度同样的逻辑在不同操作系统的文件系统语义下可能产生完全不同的安全效果。NTFS 备用数据流、8.3 短名、尾部点剥除——这些不是逻辑错误而是 Windows 文件系统的特性。如果安全检查只基于字符串匹配这些特性就成了绕过通道。跨平台 Agent 必须在安全层同时考虑多种操作系统的文件系统语义。三个可复用设计模式模式核心思想适用场景快路径/慢路径分类器简单操作快速判定复杂操作深度推理任何需要 LLM 辅助决策的系统断路器防 AI 卡死双重计数器连续总计超限回退人工任何依赖 AI 自动决策的系统危险路径免疫 bypass硬编码保护清单安全优先级高于用户偏好需要防止自我提权的系统下期预告第08篇权限状态机与渐进式授权——从用户体验到子Agent代理下一篇我们将覆盖第5章后半5.8-5.14深入六种权限模式的状态机——从 default 到 bypassPermissions 的完整转换图危险权限的剥离-恢复——进入 Auto 模式时临时禁用宽泛规则变换函数防竞态——异步门控检查的函数式解法渐进式信任 UX——从权限疲劳到协作者的交互设计子 Agent 的权限继承——多 Agent 场景下的权限传播设计模式提炼——8 个可复用的权限设计模式权限不只是允许/拒绝——它是一种用户体验一种信任建立过程一种跨 Agent 的治理框架。