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

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 路径绕过检测——这些看似"基础设施"的代码,恰恰是权限安全的底层基石。

本篇我们将深入:

  1. 规则匹配引擎——三种 Shell 命令匹配 + 四种路径前缀语义 + 六种 Windows 绕过检测
  2. YOLO Classifier——两阶段快慢路径设计的精妙之处
  3. 断路器模式——当 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 命令"当然也应该允许不带参数的gits(dotAll)标志让.能匹配换行符,从而支持包含 heredoc 的多行命令。

1.3 文件路径规则:gitignore 风格匹配

对于文件操作工具(FileEdit、FileWrite、FileRead 等),路径规则使用ignore库实现 gitignore 风格的模式匹配:

functionmatchingRuleForInput(path,context,toolType,behavior){constpatternsByRoot=getPatternsByRoot(context,toolType,behavior)for(const[root,patternMap]ofpatternsByRoot){// 移除 /** 后缀(ignore 库自动匹配子目录)constpatterns=[...patternMap.keys()].map(p=>p.endsWith('/**')?p.slice(0,-3):p)constig=ignore().add(patterns)constrelativePath=relative(root??getCwd(),absolutePath)if(ig.test(relativePath).ignored)returnpatternMap.get(matchedPattern)}returnnull}

四种路径前缀语义,覆盖从绝对路径到相对路径的所有场景:

前缀语义示例
//path相对于文件系统根目录//etc/passwd
~/path相对于用户主目录~/.ssh/config
/path相对于设置文件所在的根目录/src/**
path(无前缀)匹配任意位置*.env

1.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.实际指向.git
DOS 设备名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" │ │ │ │ 输出 <block>no</block> │ │ → 立即允许(跳过 Stage 2) │ │ 输出 <block>yes... │ │ → 进入 Stage 2 │ │ 解析失败 │ │ → 进入 Stage 2 │ └───────────────┬─────────────────────────┘ │ 不确定时 ▼ ┌─────────────────────────────────────────┐ │ Stage 2 (Thinking) │ │ max_tokens: 4096 │ │ │ │ 输出 <thinking>...</thinking> │ │ ← 深度推理 │ │ 输出 <block>yes|no</block> │ │ ← 最终决策 │ │ 输出 <reason>...</reason> │ │ ← 阻止原因 │ │ │ │ <block>no → 允许 │ │ <block>yes → 阻止 + 原因 │ │ 解析失败 → 阻止(默认安全) │ └─────────────────────────────────────────┘ 返回 YoloClassifierResult: shouldBlock, reason, thinking?, stage, usage, durationMs

Stage 1(快速阶段)是一个"快速否决器"。只用 64 个 token,遇到</block>就停止生成。对于明显安全的操作(如ls -lacat README.md),分类器通常几毫秒内输出<block>no</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.role==='assistant'){// 只提取 tool_use 块,丢弃文本块returnmsg.content.filter(b=>b.type==='tool_use')}// ...})}

关键点:助手的文本响应被故意排除在转录之外。

攻击路径通常是这样的:

  1. 恶意仓库包含精心构造的代码注释或 README
  2. Agent 读取这些内容后,将其"复述"到自己的文本响应中
  3. 如果分类器的转录包含助手的文本,攻击者的注入内容就进入了分类器的上下文

通过只保留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_TOOLS=newSet([// 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.length===0)returnnull// 解析失败 → 默认阻止returnmatches[0]![1]!.toLowerCase()==='yes'}

防御要点一:先剥离<thinking>内容再解析。分类器在推理过程中可能写出"如果这是一个恶意命令,我应该输出<block>yes</block>"这样的思考过程。如果不先移除 thinking 标签,正则表达式可能匹配到推理中的标签而非最终结论。

防御要点二:解析失败时默认阻止(Fail Closed)。如果 XML 格式异常导致解析失败,系统将其视为"阻止"而非"允许"。这是安全系统的铁律:在不确定的情况下,选择更安全的选项。


三、断路器模式:当 AI 安全员"卡死"

3.1 问题:分类器卡死

AI 分类器虽然强大,但它可能进入一种"卡死"状态:在某些上下文下,分类器反复将同一类操作判定为危险并拒绝,导致 Agent 无法完成任务。例如,分类器可能在某个上下文中将所有 npm 命令都判定为危险,即使用户的意图是正常的包管理操作。

为了防止这种情况,权限系统实现了一个断路器模式(Circuit Breaker),位于denialTracking.ts

typeDenialTrackingState={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→N+1 total: N+1→N+2 │ │ ▼ ▼ recordDenial 3 deny cons: 2→3 total: N+2→N+3 │ ▼ cons ≥ limit! shouldFallbackToPrompting = true → FALLBACK TO HUMAN

双重阈值的设计精妙之处

  • 连续 3 次:防止"当前上下文卡死"——分类器在某个对话上下文中可能产生系统性偏见
  • 总计 20 次:防止"长尾误判累积"——即使每次只拒绝一两个操作,长时间累积也说明分类器在这个会话中表现不佳

recordDenial:两个计数器都 +1;recordSuccess:只重置连续计数器,总计数保留。

3.3 不可变数据模式

断路器的状态更新采用不可变数据模式,与 React 状态管理兼容:

functionrecordDenial(state:DenialTrackingState):DenialTrackingState{// 返回新对象,不修改原状态return{consecutiveDenials:state.consecutiveDenials+1,totalDenials:state.totalDenials+1,}}functionrecordSuccess(state:DenialTrackingState):DenialTrackingState{// 优化:计数已经为 0 时返回原引用,避免无意义的 React 重渲染if(state.consecutiveDenials===0)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 的设计展示了可行的架构:

  1. 限制分类器的输入:排除助手自由文本,只保留结构化的 tool_use 块
  2. 工具自声明安全属性toAutoClassifierInput让工具参与安全决策
  3. 解析防御:剥离 thinking 标签、Fail Closed 默认值
  4. 断路器兜底: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 的治理框架。