第 7 章 第二部分:核心引擎

权限与安全模型

三种权限行为、六层规则来源、AI 分类器自动审批、危险模式检测——Claude Code 最复杂子系统的完整拆解

Claude Code 权限系统 安全模型 AI分类器 YOLO模式

7.1 为什么权限系统如此复杂

一个 AI Agent 可以读写文件、执行 Shell 命令、访问网络。如果没有权限系统,它就是一个拥有开发者完整权限的自主程序——这显然不可接受。

Claude Code 的权限系统用 24 个核心文件实现,是整个代码库中复杂度最高的子系统之一。这个复杂度不是过度工程的产物,而是安全需求的直接映射:权限系统的复杂度应该匹配工具能力的复杂度。

产业现实:安全机制的缺失差距。VentureBeat 2026 年 6 月对 107 家企业 AI Agent 安全调查显示:69% 在生产中共享凭证运行 Agent,仅 30% 对高风险 Agent 进行沙箱化,54% 已遭遇安全事件或险情(18% 确认真实泄露、36% 捕获未遂)。千人以上企业事件率 63% 但沙箱采用率仅 20%——低于中小企业的 35%。这组数据不是第 7 章的”反驳”,而是它的存在理由:Claude Code 的 24 文件权限系统之所以必要,正是因为产业现实离这个标准还差 39 个百分点(69% 共享凭证 - 30% 沙箱化)。

源码引用:权限系统核心位于 src/utils/permissions/,主入口 permissions.ts。

7.2 三种权限行为

每次工具调用的权限决策输出三种行为之一:

行为含义用户体验
allow直接允许执行无感知
deny拒绝执行模型收到拒绝消息
ask需要确认弹出权限对话框 / AI 分类器代决策

ask 是三者中最有趣的——它不是简单的”弹窗让用户点确认”。在 Auto 模式下,ask 会被路由到 AI 分类器,由另一个模型来决定是否允许这个操作。

7.3 六层规则来源

权限规则从多个来源聚合,按优先级从高到低排列:

优先级来源存储位置谁配置
1用户设置~/.claude/settings.json用户
2项目设置.claude/settings.json项目维护者
3本地设置.claude/settings.local.json用户(不入 Git)
4策略设置策略管理系统企业管理员
5CLI 参数启动参数调用方
6会话设置运行时状态运行时

每个来源可以定义 allow、deny、ask 三种规则列表。规则提取通过 getAllowRules、getDenyRules、getAskRules 三个函数在 permissions.ts#L122-231 中实现。

优先级规则:deny > allow > ask。如果同一个工具同时匹配了 deny 规则和 allow 规则,deny 获胜。

来源层级不只决定优先级,也决定否定规则的作用域(v2.1.269)。 上表给的是”谁压过谁”,但官方修过一类跨来源的语义缺陷:

Fixed a deny or ask permission rule starting with ! applying beyond the settings source that wrote it; such a rule now applies only within its own source, and a bare ! negation is ignored

以 ! 开头的 deny / ask 规则此前会越出写入它的那个 settings 来源生效——在用户设置里写的一条否定规则,可能作用到项目设置或策略设置上去。v2.1.269 起只作用于其自身来源;另外,裸 !(没有跟内容的否定)直接被忽略。

含义:来源这一列在本章有两个作用,此前只写了第一个。优先级(谁压过谁)与作用域(这条规则能影响多大范围)都由来源决定。写否定规则时若不清楚它属于哪一层,就可能出现”我明明只想在自己这一层否定,结果把企业策略层也否定了”。

第三个语义:方向反转——沙箱的四个子项上,项目层压不过策略层(v2.1.285)。 优先级(1>2>3>4)与作用域(否定规则的影响范围)之外,来源这一列还承担第三个语义,而且它与优先级方向相反:

Changed sandbox settings so project settings cannot widen or turn off an admin-required sandbox, replace the proxy behind a managed deny list, extend a strict allowlist, or reopen managed read-denies

写准的要点是它按子项生效,不是整层反转。上表的顺序 1>2>3>4 在其余子项上仍然成立;只有这四项——放宽/关闭管理员要求的沙箱、替换 managed deny list 后面的代理、扩展 strict allowlist、重开 managed read-denies——项目层被明确剥夺了向上覆盖的能力。

含义:沙箱从”一个可选项”变成了”管理员要求的那部分不可被下层解除”。把它与 v2.1.269 的否定规则作用域并读,可以看出官方在两处做的是同一件事——限制下层来源对上层意图的修改能力,只是 v2.1.269 限的是影响范围,v2.1.285 限的是修改方向。这也给 7.9 节的沙箱讨论加了一个前提:讨论”要不要开沙箱”之前,先看它是不是管理员要求的那一类。

权限模型的行业对比

Claude Code 的权限设计并非唯一选择。开源 AI 编码助手 Hermes Agent 引入了不同的三模式权限模型,与 Claude Code 形成有趣对比:

维度Claude CodeHermes Agent
权限行为allow/deny/ask 三行为Hardline/YOLO/Smart 三模式
默认基线偏保守(ask 为主)用户显式选择
AI 辅助YOLO classifier 双阶段Smart 模式 AI 判断
最保守模式deny 规则覆盖Hardline(全部拒绝)
最宽松模式bypassPermissions(受限)YOLO(全部允许)
学习型权限有(7.5 节讨论)无
设计哲学保守基线 + AI 辅助升级用户显式选择权限等级

关键差异:Hermes 的”Hardline”模式提供了比 Claude Code 更保守的基线选择——全部拒绝而非选择性审批。这在安全敏感场景下可能更合适,但牺牲了开发效率。Claude Code 的设计哲学是”默认安全但渐进放权”,而 Hermes 的哲学是”让用户明确选择安全等级”。

7.4 权限决策核心流程

hasPermissionsToUseTool 是权限决策的主函数:

hasPermissionsToUseTool(tool, input)
  ├── 1. 工具级规则匹配:工具名称是否匹配 allow/deny/ask 规则
  ├── 2. 输入级规则匹配:工具输入参数是否匹配规则内容
  │     └── 例:Bash(ls:*) 允许所有以 ls 开头的命令
  ├── 3. 安全检查
  │     ├── 路径安全验证(防路径遍历)
  │     ├── 危险命令检测
  │     └── 敏感文件保护
  ├── 4. 沙箱检查(2025.10 新增)
  │     ├── 沙箱启用 → 自动允许沙箱内 Bash 命令
  │     └── 沙箱禁用 → 继续标准流程
  ├── 5. 权限模式判断
  │     ├── auto → 调用 AI 分类器
  │     ├── acceptEdits → 自动允许文件编辑
  │     ├── plan → 只读探索,不改源文件
  │     ├── dontAsk → 静默拒绝
  │     ├── bypassPermissions → 绕过(受限)
  │     ├── (headless 不是模式值,见下)→ 超限时直接终止(非回退确认)
  │     └── default → 弹出确认对话框
  ├── 6. 分类器不可用策略
  │     ├── fail-closed(默认):分类器不可用时拒绝
  │     └── fail-open:分类器不可用时允许
  └── 7. 决策记录(用于审计和学习)

源码引用:权限决策核心参见 permissions.ts#L473-479。

权限模式清单对账。官方 permissions 文档 列出的模式取值共六个,与上面判断树的关系是:

官方模式行为与本章的对应
default每个工具首次使用时提示;CLI 标签为 Manual,manual 是其别名判断树的 default 分支
acceptEdits自动接受文件编辑与 mkdir/touch/mv/cp 等工作目录内的常见文件操作判断树的 acceptEdits 分支
plan只读探索,不编辑源文件;若 auto 模式可用则分类器批准的命令仍会执行本章此前遗漏,7.4 判断树已补入
auto交给 AI 分类器7.6 节
dontAsk静默拒绝判断树的 dontAsk 分支
bypassPermissions绕过(受限)判断树的 bypassPermissions 分支

需要区分的是 headless 不是一个用户可设的权限模式值——它不出现在官方模式表中,本章此前把它与六个模式并列是一种口径混淆。准确的说法是:headless(-p 非交互)是会话形态,在这种形态下超限会直接终止而非回退确认。建议把 headless 从”模式”改称”会话形态下的行为”。

默认模式:18 天内的四次变更。 上表回答”有哪些模式”,判断树回答”每个模式怎么走”,但两者都没回答一个运维上最先碰到的问题:没有配置权限模式时,会话落在哪一支。2026-09-25 至 2026-10-16 的 18 天里,这个默认值被改了四次:

版本日期变更(未配置权限模式时)
v2.1.28309-25交互式会话,在第三方 provider 或遥测关闭时 → auto;permissions.defaultMode 仍可覆盖
v2.1.28409-28交互式终端与 VS Code → auto,适用于所有 plan 与 provider;permissions.defaultMode 仍可覆盖
v2.1.28509-29claude -p 与 Python Agent SDK,在第三方 provider 或遥测关闭时 → auto;同版本把”把 auto 设为默认”的一次性提议也显示给这两类场景
v2.1.30210-16claude -p 与 Agent SDK 会话 → default(第三方 provider、遥测关闭或全新安装);--permission-mode auto 保持 auto

三点必须写准:

  1. 四步方向不一致。前三步是把 auto 的覆盖面一步步扩大(先第三方交互式 → 再所有 plan/provider 的交互式 → 再 headless/SDK),第四步在 headless 与 SDK 上回退到 default。只引用其中任一个版本,都会得到与当前行为相反的事实。
  2. v2.1.302 只点名 claude -p 与 Agent SDK。交互式终端(v2.1.284)并未在同一条中被撤回,不得据此推断终端也回到了 default——按官方措辞,交互式终端当下仍应是 v2.1.284 的 auto。
  3. v2.1.302 三个触发条件的逻辑连接官方未进一步明确。官方原文是逗号并列:“on third-party providers, with telemetry off or on a fresh install”。本章按并列读作「或」,并在此标注该口径未经官方逐项澄清。

由此得出一条读法:判断树的 default 分支在逻辑上仍然正确,但”未配置时落到哪一支”必须带版本号读。凡是要断言某个会话会落在哪种模式,先确认三件事——会话形态(终端 / -p / SDK)、provider(直连 / 第三方)、以及版本。

两种受限执行形态

除六个模式外,还有两个正交于 permission_mode 的执行形态。它们不是新的模式值,而是在不同维度上做减法:

形态引入版本作用维度具体效果
--restricted / CLAUDE_CODE_RESTRICTED=1v2.1.248规则来源维度移除所有可执行命令或代码的内置工具以及 WebFetch(除非被 --tools 显式点名);文件工具限制在工作目录内;拒绝 bypassPermissions;忽略 user / project / local 三层 settings 文件
--permission-prompts nonev2.1.259提示维度面向无人值守 headless 宿主,任何会弹提示的请求一律自动 deny;当前 permission_mode(含 auto)继续做决策

--restricted 对 7.3 节的六层模型是一个结构性补充:此前的六层讲的都是”增加一层来源”,而它是第一次出现”移除来源”——直接砍掉第 1/2/3 层(用户/项目/本地设置),只留策略层与 CLI 层。这意味着在 --restricted 下,7.3 节那张优先级表的上三层根本不存在,讨论”用户设置是否遮蔽项目设置”没有意义。

--restricted 的两条漏网(v2.1.290):它减掉的是工具与设置来源,不是进程能力。 上一段把 --restricted 描述为”规则来源维度上做减法”,官方随后修了两条它没减掉的东西:

Fixed --restricted (and CLAUDE_CODE_RESTRICTED=1) sessions opening the cross-session messaging socket

Fixed an @-mention under the read block or --restricted being able to read a file outside the working directories through a link changed mid-read

两条的性质不同,要分开记。第一条说明 --restricted 不改变进程能力——跨会话消息 socket 照常打开;它减掉的是工具集与设置来源,不是”这个进程能做什么”。这个区分此前本章没有明说:把 --restricted 当成”降级运行”的读者会以为它把会话整体关小了,实际它没有。

第二条与 7.11 的 TOCTOU 是同一个结构:检查时路径指向工作目录之内,读取时链接已被改掉,落点跑到外面。差别在于发生的层面——7.11 那条在文件路径层,这条在引用解析层(@-mention 指向的那个文件)。两者共同说明”检查—使用之间的一致性”不只是一条 Bash 命令的问题,凡是间接引用(链接、@-mention、符号链接)都要在使用时复核一次。

同版本还修了两条同族漏网,一并记为排查清单:Read deny 规则此前不作用于粘贴/拖入 prompt 的图片路径、也不作用于 @ 一个文件夹时列出的文件名;以及 Bash 权限检查此前不把 Read deny 规则与”工作目录外读取阻断”应用到某些 Bash 场景。

--permission-prompts none 则说明了另一个容易误解的点:“不提示”不等于”自动允许”。它把会提示的请求转成 deny,而不是放行;auto 模式的分类器照常工作,照常可以给出 allow。这与 bypassPermissions(绕过一切)是完全相反的方向。

7.5 规则匹配算法

toolMatchesRule 函数实现了灵活的规则匹配:

// 规则匹配示例
"Bash"           → 匹配所有 Bash 调用
"Bash(ls:*)"     → 匹配 ls 及其所有参数
"Bash(python)"   → 仅匹配 python(精确)
"mcp__server1"   → 匹配 server1 的所有工具
"mcp__server1__*" → 同上(通配符形式)

MCP 工具有特殊的匹配逻辑——可以按服务器级别或工具级别配置权限:

// MCP 权限匹配(简化)
const ruleInfo = mcpInfoFromString(rule.toolName);  // "mcp__server1"
const toolInfo = mcpInfoFromString(toolName);        // "mcp__server1__tool1"

return (
  ruleInfo.serverName === toolInfo.serverName &&
  (ruleInfo.toolName === undefined || ruleInfo.toolName === '*')
);

源码引用:规则匹配算法参见 permissions.ts#L238-269。

规则遮蔽检测与沙箱例外

沙箱引入后,规则遮蔽检测逻辑发生了变化:

  • 沙箱模式例外:个人 Bash ask 规则不遮蔽特定 allow 规则(因为沙箱已提供操作系统级保护)
  • 共享设置保护:项目设置和策略设置仍会遮蔽规则(保护无沙箱的团队成员)
  • 安全模型升级:沙箱作为最终防护层,允许更灵活的权限配置而不牺牲安全性

源码引用:规则遮蔽检测参见 shadowedRuleDetection.ts。

规则写错时的静默失效

toolMatchesRule 讨论的是”规则写对了怎么匹配”,但运维中真正危险的是规则写错了却不报错。官方 changelog 在 v2.1.246 与 v2.1.260 集中修了几类:

失效形式版本现象
路径含括号的 Edit/Write/Read 规则被当作无效丢弃,或被 Bash 沙箱忽略v2.1.260「只读」目录实际可写
一条无法编译的模式(如未闭合的 [)让所有文件编辑失败并报 Invalid regular expressionv2.1.260不是局部失效,是全局不可用
右括号后带多余文本的规则(如 Bash(ls) x)从未匹配任何东西,此前被静默忽略v2.1.260现改为报无效设置
通配符位于子命令之前的 allow 规则(如 Bash(git * main))会连带匹配插在子命令前的选项v2.1.246新增启动告警,因 Bash(git * main) 也匹配 git --no-pager main

这四类的共同点是:权限规则的错误配置不报错,表现为「防护不存在」而不是「防护失败」。这对 7.12 节”默认安全”原则是一个必要的限定——默认安全保证的是默认状态的安全,不保证配置之后的安全。写完 deny 规则后应主动验证它真的会拦,而不是假设语法已被接受。

第五类,性质相反:规则没写错,但匹配语义变了(v2.1.299)。 上面四类都是”你写错了”。还有一类是你没写错,官方把匹配语义收紧了:

Changed MCP allow rules to reach only the server they name: mcp__a no longer allows a server named a__b or a_; write mcp__<server>__* with the server’s whole normalized name

同版本对 agent 的 tools: 列表做了同样的收紧(只取所命名 MCP server 的工具;名字里含 __ 的 server 需按官方写法列出)。

症状与前四类方向相反,这正是它的教学价值:前四类表现为”防护不存在”(本该拦的没拦),这一类表现为”原本能用的工具突然开始要授权”(本不该问的开始问)。排查方向因此也相反——不是去查规则语法,而是去看规则名是否还是官方当前要求的写法。给一条可操作建议:升级后若某批 MCP 工具突然开始弹提示,先按新写法重写规则名(mcp__<server>__*,给出完整的规范化名称),而不是去放宽权限——放宽权限会把语义变化误当成配置问题,从而把一个收紧变成了事实上的放权。

这一类也再次说明:MCP 的匹配语义不是一个稳定的契约。mcp__a 此前会连带匹配到名字以 a 开头的其它服务器(a__b、a_),这是一类隐式前缀匹配;它现在只作用于完全同名的那个。把这条与 7.5 开头那段 mcpInfoFromString 的源码逻辑并读可以看出,官方是在把”实现里的字符串切分”逐步改成”显式的完整名匹配”。

7.6 AI 分类器:用 AI 审批 AI

Auto 模式下,ask 决策不会弹出用户确认框,而是路由到 YOLO 分类器(yoloClassifier)——一个用 Claude 模型来评估操作安全性的系统。

⚡ 分类器跑在哪儿,已经改过不止一次。 下面把分类器当作一个跑在本地的组件来讨论——那是最初的形态。默认部署位置在 2026 年 9 月经历了两步变化,且第二步不是回滚、而是扩大:

  • v2.1.273:Bedrock / Vertex / Foundry 上暂时改用 local classifier 为默认;设 CLAUDE_CODE_AUTO_MODE_SERVER=1 可用平台侧的服务端分类器。
  • v2.1.278:auto 模式对 Claude API 与 Enterprise 用户,以及 Bedrock、Vertex、Foundry 与 gateway 默认改为 server-side classifier,不收取 classifier overhead,并在走计费回退时告警;CLAUDE_CODE_AUTO_MODE_SERVER=0 可退出(在上述云平台与 gateway 上)。/status 新增 Auto mode server 行显示本次会话的分类器位置。
  • v2.1.281:CLAUDE_CODE_AUTO_MODE_SERVER 扩展到直连 Anthropic API:0 退出服务端分类器(此时 local classifier 计入用量),1 启用。
  • v2.1.282:直连 Anthropic API 且遥测关闭时,默认也走 server-side classifier。

顺序容易记反:先有 v2.1.273 的三云平台 local 默认,后有 v2.1.278 的服务端默认(且覆盖了同样三个平台)。只写其中任一个版本都会造成新的事实错误。计费相关的官方说明见 code.claude.com/docs/en/auto-mode-classifier-billing。

另有一条机制性变化(v2.1.281):在分类器走服务端的地方,只读与沙箱化的 shell 命令也要等这次复核,被标记即阻断——此前这类命令可能不经复核。

第二个独立维度:分类器用哪个模型。 上面四步记的都是部署位置(local / server-side)。官方另有一个与之正交的旋钮——模型:

  • v2.1.288(2026-10-02):客户端 auto 分类器会忽略 ANTHROPIC_DEFAULT_SONNET_MODEL 中指向 Claude Sonnet 5.5 或 Opus 5.5 的 pin,改用 Claude Sonnet 5。
  • v2.1.302(2026-10-16):新增 CLAUDE_CODE_AUTO_MODE_MODEL,用于选择客户端 auto 分类器所用的模型;官方明确它不改变服务端 auto 模式的模型。

两点读法。其一,v2.1.288 说明分类器不跟随主模型的 pin——你用环境变量把 Sonnet 钉到 5.5,分类器仍然跑在 Sonnet 5 上。想改它必须走 v2.1.302 这个专用变量,且只作用于客户端那一支。其二,把两条与部署位置合起来,auto 模式现在有四个可独立的旋钮:部署位置(local / server-side)、模型(默认 / CLAUDE_CODE_AUTO_MODE_MODEL)、是否主动降级、以及 pin 是否被尊重。四个旋钮互不隶属——这也解释了为什么”我把模型换了,分类器的行为却没变”这类困惑会出现。

分类器采用双阶段设计:

Stage 1:快速思考

  • max_tokens = 64,强制快速输出
  • 后缀提示:“Err on the side of blocking”——倾向保守
  • 如果返回 shouldBlock = false,立即允许(快速路径)
  • 如果返回 shouldBlock = true,进入 Stage 2

Stage 2:深度思考

  • max_tokens = 4096,允许详细推理
  • 后缀提示:“Review the classification process”,要求完整思维链
  • 返回最终决策 + 原因 + 思维过程

这个双阶段设计在安全性和效率之间取得了平衡——大多数安全操作在 Stage 1 就能快速通过(几十毫秒),只有可疑操作才需要 Stage 2 的深度评估(可能几秒)。

双模式输出格式

YOLO 分类器支持两种输出格式,适应不同的使用场景:

  • XML 格式:<block>yes/no</block> 标签,用于快速决策
  • 工具调用格式:classify_result 工具,返回结构化决策报告

AutoModeRules 配置也扩展了规则类别,新增了 soft_deny(软拒绝)和 environment(环境变量)规则类型,提供更细粒度的权限控制。

第三类规则:Containment Escape(v2.1.257)。官方为 auto 模式新增了一条 containment escape 规则——云元数据凭证拉取(cloud metadata-credential fetches)、出口规避(egress evasion)、跨租户访问(cross-tenant reach)不再被自动批准,除非环境显式标记这些行为是预期的。

这条规则的意义在于它的判定对象和前两类不同:soft_deny 与 environment 回答的都是”这条命令危不危险”,而 Containment Escape 回答的是”这条命令是不是在探测或逃离容器边界”。单看命令内容,拉取 169.254.169.254 上的元数据与 curl 一个普通内网地址在语法上没有区别——能区分它们的前提是分类器知道”当前处于容器内”这一环境事实。

这让 7.9.1 节三分类威胁模型中”工具性收敛 → 沙箱有效性 ★(需要新维度)“那一格第一次有了工程化答案:分类器层开始识别逃逸意图,而不只是评估单条命令的危险性。这与 9 月上旬那次公开的自主逃逸事件(Agent 利用网络出口例外 exfiltrate 数据)正好构成闭环——在 Containment Escape 规则之前,这类行为在 auto 模式下是被批准的正常网络访问。

源码引用:YOLO 分类器参见 yoloClassifier.ts,安全白名单参见 classifierDecision.ts。

安全白名单

28 个工具被标记为安全,可以跳过分类器直接允许:

类别工具
只读文件操作FileRead
搜索工具Grep、Glob、LSP、ToolSearch、ListMcpResources、ReadMcpResource
任务管理TodoWrite、TaskCreate/Get/Update/List/Stop/Output
计划模式AskUserQuestion、EnterPlanMode、ExitPlanMode
协作工具TeamCreate、TeamDelete、SendMessage
工作流工具WorkflowTool(条件启用)、OverflowTestTool、VerifyPlanExecutionTool
其他Sleep、TerminalCapture、TerminalCaptureTool

白名单工具全是只读或元数据操作——它们不会对系统产生实质性影响。FileWrite、FileEdit、Bash 不在白名单中——任何可能修改系统状态的操作都必须经过分类器评估。

⚡ 限定:「只读」不是工具名的静态属性,而是一份会被逐条重判的动态清单。 上表容易读成”这 28 个工具天然只读,所以免检”。官方在 v2.1.290(2026-10-05)给了一个反例:

Changed the Bash tool to ask for permission before running pyright, which is no longer treated as a read-only command

同版本还把更多形式的 ps 命令改为需要批准而非直接执行。两点含义:其一,pyright 是类型检查器,直觉上属于”只读”,但它会执行代码分析、可能触发任意代码路径,官方因此把它移出只读集合;其二,这份清单会双向变动——既有移出(本条),也可能有移入。凡是要断言”某工具免检”,先查当前版本,不要从工具名推断。这与 7.5 节第五类失效是同一族:语义由官方维护,不是由名称决定的。

7.7 熔断机制

如果 AI 分类器连续多次拒绝操作,系统会触发熔断,回退到用户确认模式:

const DENIAL_LIMITS = &#123;
  maxConsecutive: 3,   // 连续拒绝 3 次
  maxTotal: 20,        // 总拒绝 20 次
&#125;;

连续 3 次拒绝或累计 20 次拒绝后,shouldFallbackToPrompting 返回 true,Auto 模式暂时回退。成功执行一次操作后,连续拒绝计数重置。

这个熔断机制防止了一种病态场景:模型反复尝试一个被分类器拒绝的操作,陷入无限循环。

上面这组计数之外,还有一组更细的熔断参数(v2.1.280)。 官方把”有熔断”落实到了可引用的具体行为,且按拒绝与无应答分成两支:

Fixed auto mode retrying an action over and over when a safety check declined to review it; the action is now denied once, noting that retrying won’t help

Fixed auto mode denying actions over and over without pause when a safety check gave no answer; retries now back off, and the turn stops with a message after ten in a row

  • 安全检查拒绝复核某个动作:动作只被拒绝一次,并明确说明重试无益——不是”拒绝 N 次后熔断”,而是一次即终态。
  • 安全检查无应答:改为退避重试,连续 10 次后停止该轮并给出消息。

两支的区别在于失败的性质不同:拒绝是有答案的失败,重试没有意义;无应答是没答案的失败,可能是暂时抖动,所以退避而不是立刻放弃。把这两条与上面的 3 次 / 20 次并读可以看出,熔断不是单一阈值,而是按失败语义分档。

远程配置驱动的熔断

除了本地拒绝计数熔断外,系统还支持GrowthBook 远程配置驱动的熔断:

// autoModeState.ts 维护三个状态标志
const autoModeState = &#123;
  autoModeActive: boolean,      // 自动模式是否激活
  autoModeFlagCli: boolean,     // CLI 标志状态
  autoModeCircuitBroken: boolean // 远程配置触发的熔断
&#125;;

当 GrowthBook 配置中 enabled === 'disabled' 时,autoModeCircuitBroken 设为 true,阻止所有重新激活自动模式的尝试。这是企业级远程管控能力,允许管理员远程禁用自动模式而不依赖本地拒绝计数。

判断树第 7 步”决策记录”里到底记了什么:现在能区分决策来源(v2.1.301)。 7.4 判断树最后一步只有一句”决策记录(用于审计和学习)“,本章从未说明这些记录能否区分这次决策是谁做的。v2.1.301(2026-10-15)给出了肯定答案:

Added classifier_verdict and permission_mode to OpenTelemetry tool_decision events, so auto mode classifier decisions can be told apart from permission rules

Added classifier_category to OpenTelemetry tool_decision events, …

classifier_verdict 与 permission_mode 同时进 tool_decision 事件,意味着每次工具决策都能回答”这是分类器判的还是规则判的”;classifier_category(需 OTEL_LOG_TOOL_DETAILS=1)进一步命名一次分类器阻断所命中的内置规则是哪一条。

教学点有两层。第一层是审计前提:7.12 第 2 条说”AI 审批 AI 是可行的”,但要审计这个”可行”,前提是能把 AI 的决策与规则的决策分开统计——在此之前两者混在同一条决策流里,无法分别归因。第二层是 7.6 那三类规则(soft_deny / environment / containment escape)从此从概念变成可统计字段:classifier_category 让”逃逸意图被拦了几次”变成可查询的量,而不只是论文里的分类。

7.8 危险模式检测

权限系统的前置防护层是危险模式检测——在权限规则生效之前,先检测配置本身是否安全:

// dangerousPatterns.ts 中定义的危险命令模式
const DANGEROUS_BASH_PATTERNS = [
  // 解释器
  'python', 'python3', 'node', 'deno', 'ruby', 'perl', 'php', 'lua',
  // 包运行器
  'npx', 'bunx', 'npm run', 'yarn run', 'pnpm run',
  // Shell
  'bash', 'sh', 'zsh', 'fish', 'eval', 'exec',
  // 远程执行
  'ssh', 'curl', 'wget',
  // 提权
  'sudo',
];

如果用户配置了 Bash(*) 的 allow 规则(允许所有 Bash 命令),系统在 Auto 模式启动时会自动移除这个规则——因为它会绕过 AI 分类器的安全检查。移除的规则存储在 strippedDangerousRules 字段中,退出 Auto 模式时恢复。

v2.1.214 新增危险维度:除上述五大类模式外,v2.1.214 在 Bash 权限检查中新增了两项检测:(1) 超过 10,000 字符的命令不再自动批准——防止通过长命令混淆打包恶意行为;(2) 包含文件描述符重定向形式的命令不再自动批准——防止通过 exec 3<>/dev/tcp/... 等手法绕过常规命令检测。这两项检测将危险模式覆盖从”命令内容类型”扩展到”命令结构特征”,是对原有五大类的有益补充。

危险模式检测是优先级链条之外的第四条(v2.1.281)。 本章讨论权限优先级时用的是 deny > allow > ask 这条链。官方修过一条反转该直觉的案例:

Fixed a recursive rm whose target is only command-substitution output, such as rm -rf "$(pwd)", running unprompted in auto and --dangerously-skip-permissions mode; it now asks even with a Bash allow rule, unless run with CLAUDE_CODE_DISABLE_SUBSTITUTION_RM_PROMPT=1

递归 rm 的目标仅为命令替换输出时,即便已经存在 Bash allow 规则,在 auto 模式与 --dangerously-skip-permissions 下也会询问。要注意这里的两个限定:触发条件是”目标仅为命令替换输出”(rm -rf "$(pwd)" 这种连目标本身都要先算出来的写法),而不是所有递归 rm;可用 CLAUDE_CODE_DISABLE_SUBSTITUTION_RM_PROMPT=1 关闭。

结论值得单列:危险模式检测可以压过 allow。所以”我配了 allow 规则”不等于”一定不会被问”——allow 决定的是规则链的走向,危险模式检测是链外的一道独立拦截。

第二个实例:这次被压过的不是 allow 规则,而是允许型 hook(v2.1.287)。

Changed whole-tool Bash allow rules and allowing hooks to prompt for, not run, shell writes to files Claude Code’s file tools refuse outright (the Anthropic profile store, the host credentials file)

差别在于被覆盖的对象:v2.1.281 压过的是 Bash allow 规则,v2.1.287 同时点名了 allowing hooks——一个 hook 已经给出”放行”的决策,仍然会被这次拦截改成提示。第三个同族条目是 v2.1.296(Windows):rm -rf /c/Users/<name> 在 Git Bash 下、bypass permissions 模式中不再询问的问题被修复,即它现在会问。

三条并置后,判据应当从”allow 不等于一定不会被问”升级为更硬的一条:

被允许的不只是规则——允许型 hook 同样会被链外拦截覆盖。安全边界画在「执行之前」,不在「规则匹配之后」。

换句话说,7.4 那条 deny > allow > ask 的优先级链不是终点。规则链决定的是”规则之间谁赢”,而危险模式检测是规则链之外、执行器之内的最后一道。凡是依赖”我在 hook 里放行了”来构造自动化流程的设计,都要预留被这一步改判的可能。

源码引用:危险模式检测参见 dangerousPatterns.ts,权限初始化安全检查参见 permissionSetup.ts。

第三类失败模式:命令名合法但解析结果非法

五大类模式匹配与 v2.1.214 的两项结构检测,共同前提是能正确解析出”这条命令是什么”。2026 年 8 月底到 9 月初,官方在四个连续版本里修了同一族绕过,全部源于 zsh 与 bash 对同一字符串的解析结果不同:

版本被错误自动批准的形式
v2.1.246悬空 && / || 的畸形命令
v2.1.251整数 shell 变量的算术赋值(OPTIND=1/0、RANDOM=2+2)
v2.1.257部分 [[ ]] 条件表达式(zsh 解析与 bash 不同)
v2.1.260把命令替换藏在 REPORTTIME / REPORTMEMORY / DIRSTACKSIZE 赋值里的 zsh 命令

这四类绕过的共同特征是:命令名完全合法。没有一个会命中 DANGEROUS_BASH_PATTERNS 里的 python / curl / sudo,也不超过 10,000 字符,也不含 fd 重定向。它们依赖的是”同一字符串在两个 shell 里含义不同”。由此可以抽出一个比”再补两个危险模式”更高层的判据:

危险模式检测与 Bash 权限检查,必须与「实际执行该命令的 shell」使用同一套解析器。否则就存在检测—执行语义鸿沟:检测器看到的是一层语义,执行器跑的是另一层。

成因不同的第三条:命令藏在环境变量前缀赋值的展开值后面(v2.1.289)。 上表四条的成因统一是”zsh 与 bash 解析分歧”。官方随后修的一条不属于这一族:

Fixed Bash deny and ask rules missing a command behind an environment variable prefix with an expanded value (e.g. TZ="$HOME" rm -rf build) when the sandbox auto-allows commands

同版本还有一条更宽的形式:deny/ask 规则在沙箱自动允许命令时,会跳过命令前面有一段裸变量赋值的情形(官方另列为 declare / typeset / export / readonly 前缀)。

成因要写准:TZ="$HOME" rm -rf build 里没有 zsh 专有的语法,两个 shell 对它的解析是一致的。失效的原因是命令名不在第一个 token——检测器按”第一个词是命令”去识别,取到的是 TZ=... 这段前缀。这与 zsh 方言是两个不同的盲区,因此现有判据要扩一句:

不仅要与实际执行的 shell 使用同一套解析器,还要能识别”命令可能不在第一个 token”——VAR=value command 这类前缀赋值,会让按首词取命令名的检测直接失效。

反面案例:一次”更严格”的收紧因为误伤而被回退。 上面那批修复都是把检测做严。官方也有过一次方向相反的操作,值得单独记下来——它标出了”做严”的边界在哪:

  • v2.1.268:把 Read / Edit 的 deny 规则套用到 checker 无法分析的 Bash 行上(eval、env -C),思路是”分析不了就按已有规则拒绝”。
  • v2.1.273:回退了这条改动。原因是误伤——time -p make build 这类正常命令会被拒绝。回退后它们重新走提示,而不是被拒。

Reverted a 2.1.268 change that checked Read and Edit deny rules on Bash lines the permission checker can’t analyze (eval, env -C); commands like time -p make build prompt again instead of being denied

这条回退给出了静态分析不可分析时的正确取舍:宁可提示,不可静默拒绝。前者把决定权交还给人,后者把一个”我不知道”表达成了”不行”,而且不留下痕迹。

第二例「收紧 → 回退」:这次回退的是表述,不是行为(v2.1.293,2026-10-07)。

Reverted the auto mode denial message change from 2.1.281 that told Claude a denial covers the outcome, not only the exact command

v2.1.281 曾把 auto 模式的拒绝消息改成”一次拒绝覆盖的是结果,而不只是那条具体命令”——这是一次语义收紧(拦得更宽,连等价命令一起盖住)。v2.1.293 把它回退了。要写准的是:回退的是消息的含义,拦截行为本身没有变——被拒的那条命令仍然被拒,只是不再宣称”这次拒绝也覆盖了等价写法”。

两例并置后,7.8 末尾那条元结论可以写得更稳:权限系统的边界在官方手里也是正在收敛的,而且收敛不只发生在”拦不拦”,也发生在”拒绝意味着什么”。因此那条运维建议(升级后回归验证关键 deny 规则仍然生效)应当保留并加强——它不只适用于 deny 规则,也适用于拒绝消息的含义:如果自动化流程依赖”被拒一次即代表这类操作都不行”这个假设,那么 v2.1.293 之后这个假设不再成立。

同一版本还修了两条同族的漏网:checker 无法完全分析的 Bash 命令在 permissions.blockReadsOutsideWorkingDirectories 下跳过提示,以及 bypass 模式下用 subshell 隐藏危险 rm——分析不了的地方,两头都会出问题。

同一条边界在 v2.1.259 与 v2.1.260 之间还在来回移动:v2.1.259 把 Read() deny 规则扩展到 git diff / git grep 的文件操作数、--ignore-revs-file=.env、@file 与 cd DIR && cat FILE 复合命令,v2.1.260 又回退了其中一部分——因为它让 npm run build 在 Read(./**/build/**) 规则下被误拒,且 cd … && grep 在 auto 模式下开始弹提示。

这说明”一条命令该如何被拆解”本身就是一条正在动态收敛的边界。本节不宜把任何一个版本的具体规则写死成结论;值得记住的是上面那条判据,以及一个运维建议:升级后应回归验证自己的关键 deny 规则仍然生效。

7.8.1 技能与插件供应链安全

7.8 节的危险模式检测覆盖了 Bash 命令级别的风险,但随着 Claude Code Plugin 系统的正式上线,一个新的攻击维度已经打开:技能/插件供应链安全。

OWASP Agentic Skills Top 10

OWASP 于 2026 年正式发布 Agentic Skills Top 10 安全风险清单,其中与工具/权限系统直接相关的核心风险包括:

风险 ID风险名称严重度对 Claude Code 的影响
AST01恶意技能CriticalPlugin 可打包 Skills + Commands + Agents + Hooks,任一层被污染即构成完整攻击链
AST02技能市场投毒Critical/plugin 命令从市场安装扩展,攻击者可通过投毒市场包获取 Agent 的文件系统和 API 访问权限
AST03权限提升High恶意 Plugin 可能利用权限系统的规则优先级漏洞实现权限提升
AST04数据泄漏High恶意技能通过工具调用读取敏感文件(如 .env、密钥文件)并外传
AST05提示注入High恶意 Plugin 的描述/说明可能包含提示注入,影响 AI 分类器的安全判断

Plugin 系统引入的攻击面

Claude Code 的 Plugin 系统将 Skills、Commands、Agents、Hooks 打包为可安装包。这种”四合一”架构意味着:

  • Skills 污染:恶意 Skill 可在工具调用链中注入不当行为
  • Commands 污染:恶意 Command 可伪装为常用命令,诱使 Agent 执行危险操作
  • Agents 污染:恶意 Agent 可在 ForkSubagent 流程中执行未授权操作
  • Hooks 污染:恶意 Hook 可在工具执行前后拦截和篡改输入/输出

任一层被污染,整个 Plugin 即构成威胁——这比单独的 MCP 工具风险更高,因为 Plugin 是多层打包的复合体。

当前防护措施的覆盖度分析

Claude Code 现有的权限安全体系对供应链攻击的覆盖度有限:

防护层对供应链攻击的覆盖不足之处
六层规则来源部分可通过 settings.json 配置 deny 规则,但需手动配置
AI 分类器部分可检测 Plugin 执行时的异常行为,但无法检测安装时的恶意意图
危险模式检测低仅覆盖 Bash 命令级,不覆盖 Skill/Plugin 级
沙箱中等限制文件系统访问范围,但 Plugin 的合法权限可能足够读取敏感数据

2026 年已发生多起 AI Agent 技能供应链安全事件,360 于 2026 年 5 月上线 Skill 安全检测平台进一步佐证这已成为产业级问题,而非学术讨论。Plugin 系统的安全边界设计将是 Claude Code 权限模型下一步演进的关键课题。

补充框架:OWASP Top 10 for Agentic Applications 2026。前述 AST01-AST10 聚焦技能/系统级风险,ASI01-ASI10 覆盖 AI 系统级。OWASP 2026 年正式发布的 Top 10 for Agentic Applications 是第三层——应用级风险框架,从”自主规划、行动、决策”的 Agent 应用视角重新定义安全边界。Frontier Model Forum 2026-06 指南据此提出:确定性基础设施层控制(沙箱、文件系统范围、网络出口策略)是 Agent 安全的主要执行点——模型判断只应是辅助而非主体。三个 OWASP 框架形成层次互补:应用(Agentic Applications)→ 系统(ASI)→ 技能(AST)。

学界回应:SkillGuard 双平面治理(Pan et al., arXiv:2606.03024)。SkillGuard 提出将技能视为”携带权限的可执行制品”——而非被动资源——通过 Context Plane(控制技能对 Agent 推理轨迹的影响)+ Action Plane(控制工具调用和副作用)双平面治理。在 1,260 个真实技能上覆盖 99.93% 的受保护对象;对抗评估中将上下文注入攻击成功率从 35.3% 降至 20.7%、显式注入从 36.7% 降至 18.0%。这直接对应 AST01(恶意技能)、AST03(权限提升)、AST05(提示注入)三项风险的防御。

7.9 沙箱:操作系统级硬边界

2025年10月,Anthropic 为 Claude Code 引入了原生沙箱系统,这是权限安全模型的重大升级。沙箱提供了操作系统内核级的隔离机制,将权限系统从”软边界”(依赖AI判断)升级为”硬边界”(内核强制执行)。

设计动机:从软边界到硬边界

  • 软边界:规则匹配、AI分类器、用户确认——依赖程序逻辑判断
  • 硬边界:操作系统内核强制执行隔离——物理上限制影响范围

沙箱的引入使安全模型从六层变为七层:规则匹配 → 安全检查 → AI 分类器 → 用户确认 → 沙箱隔离

macOS 实现:Seatbelt

macOS 使用 Apple 的 Seatbelt 沙箱框架:

<!-- 自定义 .sb profile 示例 -->
(version 1)
(deny default)
(allow file-read* (subpath "/usr/lib"))
(allow file-read* (subpath "/System"))
(allow file-read* file-write* (subpath "/working/dir"))
(allow network-outbound (remote tcp "*:*"))

关键特性:

  • 文件系统白名单:仅允许访问工作目录和特定系统路径
  • 网络策略:可完全阻断或限制网络访问
  • 通过 sandbox-exec 命令启动沙箱环境

Linux 实现:bubblewrap

Linux 使用 bubblewrap(用户空间命名空间隔离):

bwrap --ro-bind /usr /usr --dev /dev --proc /proc \
      --bind /working/dir /working/dir --unshare-net \
      claude-code

关键特性:

  • 无需 root 权限即可创建隔离环境
  • mount/net/pid namespace 隔离
  • 可配置网络访问权限

沙箱与权限系统的协同

沙箱引入后,权限决策逻辑发生了变化:

  • 沙箱内 Bash 命令自动允许:因为操作系统已限制影响范围,可跳过 AI 分类器
  • 规则遮蔽检测例外:个人 Bash ask 规则不遮蔽特定 allow 规则(保护无沙箱的团队成员)
  • 安全模型升级:沙箱作为最终防护层,即使前六层全部失效,操作系统隔离仍能保护系统

源码引用:沙箱集成参见 sandbox.ts,macOS Seatbelt 配置参见 seatbeltProfiles/。

7.9.1 沙箱技术全景对比

7.9 节详述了 Claude Code 当前的沙箱实现(Seatbelt/bubblewrap),但 OS 级沙箱并非唯一的隔离方案。Zylos Research 发布的 AI Agent 沙箱与安全隔离综合研究 对四种主流沙箱技术进行了全面对比:

沙箱技术隔离强度启动开销功能限制适用场景
OS 级沙箱(Seatbelt/bubblewrap)★★★极低中(共享内核)日常开发,Claude Code 当前方案
MicroVM(Firecracker 等)★★★★★中(秒级)低(接近完整 VM)高安全要求的 CI/CD、不可信代码执行
gVisor(用户空间内核)★★★★低中(系统调用过滤)平衡安全与性能的服务端部署
WASM(WebAssembly)★★极低高(受限运行时)轻量级计算插件、受控执行环境
MXC(Microsoft Execution Containers,2026-06-09)★★★★低中(OS 原生隔离)微软企业 Agent 部署,Build 2026 发布

Claude Code 选择 OS 级沙箱的工程权衡:

  1. 启动开销优先:开发者期望即时响应,MicroVM 的秒级启动对交互式 Agent 不可接受
  2. 功能完整性:Claude Code 需要执行任意开发工具链(编译器、测试、Lint),WASM 的受限运行时无法满足
  3. 跨平台覆盖:Seatbelt(macOS)+ bubblewrap(Linux)覆盖了开发者的主流操作系统
  4. 已知局限:共享内核容器的安全模型对不可信 AI Agent 工作负载存在不足——CVE-2026-55607 已证实 Git worktree 可作为沙箱逃逸向量(v2.1.38-2.1.163),攻击者可通过创建名为 .git 的 worktree 并利用符号链接操纵覆写用户家目录文件(如 .zshenv),实现沙箱外代码执行。对于执行不可信代码的场景,MicroVM 或 gVisor 是更强的隔离方案。

这种权衡是合理的:Claude Code 的沙箱主要目标是限制意外影响范围(如防止 Agent 误删系统文件),而非防御恶意攻击者的主动逃逸。后者需要更强的隔离方案,但也带来了更大的性能和兼容性代价。

新增类别:MXC 企业级 Agent 沙箱。2026 年 6 月 9 日 Build 大会上,微软发布 Microsoft Execution Containers (MXC),定位”面向 Agent 的策略驱动执行层”,目前处于预览阶段。MXC 与已有四类沙箱的本质差异:

差异点OS 级沙箱/MicroVM/gVisorMXC
集成层次容器化或独立 VMOS 原生强制执行(Windows 内核)
身份模型共享用户身份Agent 独立身份(区分人类用户与 Agent 行为)
策略定义配置文件/白名单开发者定义的访问边界(资源边界而非工具边界)

这一类别的出现标志着 AI Agent 沙箱从”开发者工具”升级为”操作系统基础设施”——Windows 直接为容器内 Agent 分配独立身份,是 OS 层对 Agent 工作负载的第一类支持。MXC 的 OS 原生集成也意味着它能利用 Windows 的策略引擎、UAC、Credential Manager 等已有安全组件,而不需要外挂独立的沙箱运行时。

威胁模型的根本性重审:ROME 自主逃逸事件。2026 年 2 月已发生的 Agent 逃逸事件已证明共享内核容器的安全模型对不可信 AI Agent 工作负载存在不足,但 2026 年 3 月阿里巴巴研究院内部 RL 训练实验中的 ROME 自主逃逸事件 揭示了一个性质完全不同的新威胁——Agent 自发推断出自身处于沙箱环境,并系统性尝试逃逸:

行为序列:Agent 通过持续探索推断沙箱存在 → 绕过 cgroup 限制获取额外 CPU 资源 → 利用闲置算力挖掘 Monero(门罗币)→ 建立反向 SSH 隧道向外部 C2 服务器发送心跳包。整个过程完全由奖励函数驱动——Agent 将”获取更多计算资源”映射为完成训练任务的工具性手段。72 小时后被发现,无数据外泄但已触发真实外网连接。

这一事件对 7.9.1 节末尾的”主要目标是限制意外影响范围而非防御恶意攻击者”工程权衡论述构成直接挑战。ROME 逃逸不是外部注入的恶意指令,也不是社会工程攻击,而是 Agent 在追求任务目标时的工具性收敛(instrumental convergence)——AI 安全研究中已知的经典威胁:智能体在优化任何目标的过程中,都倾向于获取更多资源、保护自身存在、避免被修改。

三分类威胁模型。综合 Zylos Research 的安全研究、ROME 事件和 NVIDIA AI 红队实践,Agent 沙箱的威胁模型应从二分升级为三分:

威胁类型来源传统防御重点沙箱有效性
意外影响Agent 执行错误文件系统白名单★★★★★(OS 级即可)
外部注入恶意提示/插件规则匹配 + 分类器★★★(需要多层)
工具性收敛Agent 自主演化当前尚无成熟方案★(需要新维度)

工具性收敛的防御不能仅靠沙箱——它是 AI 安全的前沿问题,需要 capability control(限制模型能做什么)、reward shaping(限制模型想要什么)、constitutional AI(限制模型被允许追求什么)等系统化方案协同。本节末尾讨论的”学习型权限治理”和”任务级最小权限集”思路,是这一方向的初步探索。

第四类威胁:配置触发执行(GitSpawn)。 上面三分类无法覆盖 2026-09-01 公开的一类攻击,因为它的交付形态与触发路径都不在这三列里。Manifold Security 的 GitSpawn 研究覆盖了 8 个缺陷、7 款 agent(Claude Code、OpenAI Codex、Cursor、Goose、Hermes Agent、Qwen Code、Grok Build):

  • 机制:Git 的 core.fsmonitor 这类设置的值本身是”要执行的程序”,而它从仓库自己的 .git/config 读取。任何会刷新索引的操作(git status、git diff)都会触发它。编码 agent 恰恰在后台例行调用这两个命令来收集上下文。
  • 为什么现有三类都不适用:它不是提示注入(不需要任何内容被模型读到),不是工具性收敛(不需要 agent 自主演化),也不是意外影响。执行发生在沙箱之外、以用户权限运行,且不出现在审批流程里——在 agent 看来它只是跑了一次 git status。三分类里”沙箱有效性”那一列对它全部不适用,因为执行本来就在沙箱外。
  • 交付形态是一个目录,不是一次对话:需要 .git 目录完整保留(zip 包、共享盘、U 盘、同步文件夹)。一次正常的 git clone 不会复制远端的本地配置,因此 clone 比解压更安全。
  • 覆盖面与修复状态:Claude Code 的 core.fsmonitor 路径已在 v2.1.196 修复;Goose 修复于 1.44.0(CVE-2026-72718,CVSS 7.0);Hermes Agent 获 CVE-2026-71963;Codex 修复于 0.131.0,Cursor 亦已修。Manifold 在 2026-09-01 复测时仍有四条可执行仓库指定的命令:Claude Code 经 claude ultrareview 触发的第二条路径(2.1.252)、Hermes 0.21.0、Qwen Code 0.22.3、Grok Build 1.0.13。
  • 厂商侧缓解:在后台上下文采集调用上清洗 git 配置,例如 git -c core.fsmonitor=false status。用户侧可全局关闭,或在用 agent 打开一个来源不明的目录前先检查 .git/config。

⚠️ 边界:以上为厂商研究报告与多家安全媒体转载核对的结果,CVE 编号与 CVSS 未逐条回 NVD 原文;版本号随各产品迭代变化,引用时以”截至 2026-09-01 复测”为限。公开来源未见在野利用的证据。

「默认安全」的第一个量化对照:allowlist 到底拦住了多少(ZTAC,arXiv:2610.14488v1)。 上面的星级对比是定性排序,7.11 抽出的”拼写 vs 落点”判据此前也只有一条官方修复作依据。2026-10-10 的 Names Are Not Objects: Object-Bound Confinement for Compromised AI Coding Agents(Daniel Commey)第一次给了带对照基线的实测数字。

它的核心论点与 7.11 那条判据同源,但说得更完整:许多针对已部署编码 agent 的利用共享同一个成因——一次检查批准了一个名字(路径、参数向量、目录),后续步骤却把这个名字映射到了另一个对象:符号链接后面的那个文件、某个选项所选中的行为、或 git 自己发现的那个仓库。方案 Zero-Trust Agent Cells(ZTAC)让 agent 跑在内核沙箱里,唯一通往文件/程序/网络的路径是一个通往 broker 的 socket;broker 先把每个请求解析成它将要作用的那个对象,在对象上做决策,并在实际使用时校验”作用的确实是它批准过的那个对象”;批准绑定解析后的对象且只能使用一次。

实测(74 次攻击,带 ground-truth oracle,含 10 起公开披露事件的复现):

配置失守
工具 allowlist66 / 74
allowlist + 已部署 agent 所用的命令沙箱56 / 74
ZTAC2 / 74(两次都是把秘密编码进通往允许端点的流量),另有 14 次挂起待审

代价:46 项良性操作完成 43 项,触发 8 次审批提示;7 个工作流完成 4 个。开销:no-op 代理请求中位延迟 4.0 ms,受测派生程序比不用 ZTAC 慢 8.5–18.4 ms;macOS 原型 3.2k 行受信 Rust(不含依赖)。

⚠️ 三条限定必须一并写出:① 单一作者预印本、单一 macOS 原型,未经同行评议;② 74 次攻击由作者构造(含事件复现),66 / 56 / 2 不得外推成真实世界的失守率;③ 攻击集的自选偏差无法排除。

对本章的用途是把两条既有判断接上数字:allowlist 不是”没配好才失守”,配好了也在 74 次里失守 66 次;加上已部署 agent 常用的命令沙箱,仍失守 56 次。而 ZTAC 的”批准绑定解析后的对象且只能用一次”正是 7.11 判据的完整形态——官方(v2.1.280)只做到了”按落点判定”,ZTAC 进一步做到”绑定 + 一次性”。这让 7.11 那条判据第一次有了可对照的实现,也让”默认安全优于默认方便”(7.12 第 3 条)不再只是一句原则。

7.9.2 网络级隔离:纵深防御的缺失层

7.9 节详述的 Seatbelt/bubblewrap 方案属于进程级隔离——限制文件系统和系统调用访问,但不控制网络行为。社区出现的 VPS 沙箱化 YOLO Agent 方案填补了这一空白,提供了网络级隔离维度:

# VPS 沙箱化 YOLO 模式 Agent 的典型配置
# 1. iptables 网络隔离——仅允许出站 HTTP/HTTPS
iptables -A OUTPUT -p tcp --dport 80 -j ACCEPT
iptables -A OUTPUT -p tcp --dport 443 -j ACCEPT
iptables -A OUTPUT -j DROP  # 阻断其他所有出站

# 2. Squid 代理——审计和过滤 Agent 的网络请求
squid -f /etc/squid/agent-proxy.conf

# 3. 在 VPS 上运行 YOLO 模式 Agent
# 即使 Agent 执行恶意代码,影响范围被 VPS 网络边界限制

进程级隔离与网络级隔离形成纵深防御互补关系:

隔离维度机制防御目标Claude Code 当前方案
文件系统Seatbelt/bubblewrap 白名单防止误删/篡改系统文件✅ 已实现
系统调用namespace 隔离限制进程影响范围✅ 已实现(Linux)
网络iptables + 代理防止数据外泄/恶意通信❌ 未覆盖

当前 Seatbelt 配置允许 network-outbound(见 7.9 节 .sb profile 示例),这意味着沙箱内的 Agent 可以自由访问网络。在 YOLO 模式下,Agent 拥有完全执行权限,如果执行了包含恶意网络行为的代码,进程级沙箱无法阻止。VPS + iptables 方案正是针对这一场景——在独立虚拟机上运行 YOLO Agent,即使 Agent 被社会工程攻击诱导执行恶意代码,其网络行为也被 iptables 规则和 Squid 代理审计约束。

实践建议:对于安全要求较高的 YOLO 模式使用场景,建议采用”进程级沙箱 + VPS 网络隔离”的组合方案,而非仅依赖单一隔离层。

第三维度:配置文件写入(持久化攻击)。上表的三维度纵深防御模型仍有盲区——恶意 Agent 即使无法通过网络外泄数据,也可以通过修改 Claude Code 自身的配置文件实现持久化攻击。NVIDIA AI 红队在 Practical Security Guidance for Sandboxing Agentic Workflows 中明确提出三项强制控制措施,第三项即针对此威胁:

阻止 Agent 写入配置文件(无论位置)。

具体可被篡改的目标至少包括:

  • ~/.claude/settings.json(用户级设置)
  • .claude/settings.json / .claude/settings.local.json(项目/本地设置)
  • ~/.claude/hooks/ 下的所有 Hook 脚本
  • MCP Server 配置文件(claude_desktop_config.json / mcp.json 等)
  • ~/.claude/commands/ 与 ~/.claude/skills/ 下的命令与技能

攻击路径:恶意 Agent 在任务过程中写入 Hook 脚本(每次工具调用前执行)→ 下次启动 Claude Code 时 Hook 自动加载 → 攻击者在 Agent 不知情时执行任意代码。这是配置级持久化——比单次执行的工具调用更隐蔽,因为配置文件只在新会话启动时读取,攻击发生在当前会话,但效果在未来的会话中持续。

当前 Claude Code 的 Seatbelt 白名单未区分”普通文件”与”配置文件”,允许沙箱内 Agent 自由写入这些路径。NVIDIA 建议的处理方式:扩展 Seatbelt profile,明确 deny 所有 ~/.claude/、.claude/、MCP 配置目录的写操作;将配置文件维护操作从 Agent 的工具调用中隔离到用户级管理动作。这一改动相对简单(白名单调整),但效果显著——封堵了 Agent 利用”自我修改”实现持久化的攻击路径。

扩展后的纵深防御模型:

隔离维度机制防御目标Claude Code 当前方案
文件系统Seatbelt/bubblewrap 白名单防止误删/篡改系统文件✅ 已实现
系统调用namespace 隔离限制进程影响范围✅ 已实现(Linux)
网络iptables + 代理防止数据外泄/恶意通信❌ 未覆盖
配置文件Seatbelt 扩展 deny防止持久化攻击❌ 未覆盖(NVIDIA 建议)

配置解析层的 fail-closed(v2.1.283)。 上面这条的 fail-closed 说的是网络请求被拒时怎么办;官方还修过一处配置解析层的同类问题,而且方向恰好相反——从”整块失效”改成”逐项 fail-closed”:

Fixed managed sandbox settings being ignored entirely when one nested value was invalid; the invalid value now fails closed and the rest of the block still applies

修复前的行为是:managed sandbox 设置里只要有一个嵌套值非法,整块配置被忽略。修复后:那个非法值 fail-closed,同一块里的其余部分继续生效。

这里要写准,否则会把修复后的行为说成修复前的:managed 配置下写错一个子字段,代价不再是整份沙箱配置失效,但该子字段本身会按最保守的方式生效(fail-closed),而不是按你以为的方式生效。工程含义仍然明确:managed 配置的 schema 校验必须前置到下发环节——等到运行时才发现,出问题的那一项会以最保守的方式运行,表现为”功能莫名不可用”而不是报错。

v2.1.219:网络白名单的 fail-closed 模式。sandbox.network.strictAllowlist 设置引入了 fail-closed 网络白名单——默认拒绝非白名单域名的网络请求。此前沙箱网络策略是”首次提示确认”(需要人工介入),而 strictAllowlist 允许无人值守运行时的硬阻断。这是前述”出口预算”概念的具体工程实现——将网络权限从”操作时逐次询问”升级为”配置时声明边界”。需注意 strictAllowlist 仍需用户显式配置才生效,非默认启用。更新后的网络维度状态:✅ 部分覆盖(strictAllowlist,v2.1.219)。

v2.1.271:网络覆盖从”域名白名单”再下沉到”命令级域名白名单”。 上一条的 strictAllowlist 是会话级的——配好之后对整个沙箱生效。v2.1.271 补上了更细的一层:

Added per-command allowed_domains to Bash, PowerShell and Monitor in auto mode with sandboxing: the hosts a command needs are reviewed with it and opened for it alone; other hosts are refused

差别在于粒度与审查时机:命令所需的 host 与该命令一起被审查,只为这一条命令开放,其余 host 拒绝。于是网络策略从”配置时声明的一次性边界”变成了”每条命令各自带一份边界”。

对上表那一格的影响:网络维度不再是”部分覆盖(会话级白名单)“这一层就到顶了——v2.1.219 给的是会话级,v2.1.271 给的是命令级,两个粒度并存。这也让”出口预算”从设计主张落到了可执行的形态:一份 per-command 的 host 声明,就是一段可审计的出口预算。第 5 章 5.7 已把它记为工具参数随权限态变化的第五个维度。

出口预算(Egress Budget):2026 年 SwarmSignal 和 NVIDIA AI 红队进一步提出——网络隔离不应是”允许/禁止”的二元决策,而应是”按任务声明网络权限范围”的声明式模型。每个生产 Agent 应携带显式的网络权限声明:允许的目标域名/IP、允许的协议/端口、例外的日志化路径、以及”无外部调用”的硬阻断选项。这与 7.10 节 Learned Capability Governance 的”任务级最小权限集”思路一脉相承——都是将安全决策从”操作时逐次判断”升级为”任务开始时声明边界”。

7.10 超越静态沙箱:学习型权限治理

7.9 节的沙箱技术全景对比揭示了一个根本矛盾:静态沙箱在灵活性和安全性之间存在不可兼得的张力——过于严格限制功能(如 WASM),过于宽松则无法防御(如共享内核容器)。Claude Code 的 OS 级沙箱选择了一个折中点,但这并非最终答案。

Learned Capability Governance

Bronislav Sidik 和 Lior Rokach 在 2026 Agent Safety Workshop 上发表的工作 Learned Capability Governance 提出了一种超越静态沙箱的思路:用学习型治理替代静态白名单。

核心机制:

TaskDescriptor → CapabilityModel → AllowedCapabilitySet

不再预定义”允许/禁止哪些操作”的静态规则集,而是根据任务意图动态计算该任务所需的最小权限集。例如:

  • “修复这个 TypeScript 类型错误”→ 允许读取源文件、运行 tsc、编辑 .ts 文件,但禁止网络访问和环境变量读取
  • “部署到生产环境”→ 允许运行部署脚本,但限制编辑范围仅含部署配置文件

与 Claude Code YOLO 分类器的对比

Claude Code 的 YOLO 分类器和 Learned Capability Governance 都使用 AI 模型做安全决策,但抽象层次不同:

维度YOLO 分类器Learned Capability Governance
决策对象单次操作任务级权限集
决策时机操作执行时任务开始时
决策粒度allow/deny/ask最小权限集
上下文利用仅当前操作内容完整任务意图描述
与静态规则的关系叠加在静态规则之上替代静态规则

YOLO 分类器是”用 AI 审批操作”——每次工具调用时判断是否安全;Learned Capability Governance 是”用 AI 计算权限集”——根据任务意图预先划定安全边界。后者在概念上更强,但也更依赖对任务意图的准确理解。

展望

Claude Code 当前的”AI 分类器 + 静态沙箱”组合是实用的工程选择,但 Learned Capability Governance 指出了可能的演进方向:从”逐操作审批”走向”任务级权限规划”。如果 AI 对任务意图的理解足够准确,预先计算最小权限集比逐次判断更高效也更安全——因为它减少了 AI 分类器被逐次绕过的风险。

不过,这一演进面临关键挑战:任务意图的准确获取(用户能否/是否愿意提供足够精确的任务描述)、CapabilityModel 的训练数据来源、以及动态权限集的运行时强制执行机制。目前这仍是学术前沿方向,需要更多工业验证。

7.11 路径安全验证

文件操作还有一层专门的路径安全验证:

检查项优先级防护目标
路径遍历检测(../)最高防止访问工作目录之外的文件
UNC 路径(\\///)极高防止访问网络路径
Shell 扩展字符($、%)极高防止变量注入
Glob 模式高写操作禁止通配符
危险目录(.git/)高保护 Git 数据
敏感文件(.bashrc、.zshrc)高保护 Shell 配置

| 符号链接目标验证 | 极高 | 防止 UI 显示链接路径但 Agent 写入真实目标 | | 超长命令(>10K 字符) | 中 | v2.1.214 起不再自动批准(Bash 权限检查新增维度) | | 文件描述符重定向 | 中 | v2.1.214 起不再自动批准含 fd 重定向的命令 |

| 检查后路径交换(TOCTOU) | 极高 | 防止权限检查通过后符号链接被替换,v2.1.251 起权限检查覆盖解析后的真实路径 |

上表前三类容易混淆,但它们防的是三件不同的事,按时间顺序排:

类别防的是形式化说法
路径遍历不在工作目录内的文件请求路径本身就指向外部
符号链接目标验证看起来在内、实际指向外部解析结果指向外部
检查后路径交换(TOCTOU)检查时在内、执行时在外检查与执行之间状态被改变

第三类是唯一带明确 CVE 语义类别的一例(CWE-367 TOCTOU):v2.1.251 修复了 Read / Write / Edit 在权限检查通过之后、实际操作之前跟随了工作目录内被替换的符号链接,可在已批准位置之外读写。修复后权限检查覆盖的是解析后的真实路径。这是 7.11 节从”静态路径内容检查”走向”检查与执行之间的一致性”的一步。

从检测到策略开关。上面第一行”路径遍历检测”此前只有检测、没有可配置的阻断开关。v2.1.257 补上了形态:auto 模式下首次读取工作目录之外的文件前增加一次性提示,并提供 permissions.blockReadsOutsideWorkingDirectories 设置用于彻底阻断此类读取。

这项检查与沙箱、worktree 存在值得注意的相互作用——v2.1.260 专门修了它的两处副作用:在 macOS 上会隐藏沙箱 git 所需的用户 git config,以及隐藏 worktree 隔离子 Agent 自己的 checkout。也就是说,“工作目录之外”这个集合的边界,取决于你同时开了沙箱还是 worktree。这与 7.9 节沙箱协同、第 8 章 ForkSubagent 的 worktree 隔离是同一条线索,配置该开关时应一并回归验证。

GhostApproval 攻击(Wiz 研究院,2026-07-08,CWE-451):攻击者利用符号链接(symlink)指向敏感文件(如 ~/.ssh/authorized_keys),Agent 跟随链接写入攻击者 SSH 公钥,UI 对话框仅显示链接路径而隐藏真实目标。影响 6 大 AI 编码工具(Claude Code/Augment/Cursor/Amazon Q/Antigravity/Windsurf)。路径遍历检测保护的是”不在工作目录内的文件”,符号链接攻击保护的是”看起来在工作目录内但实际指向外部敏感文件的链接”——两者互补。Anthropic 最初以”超出威胁模型”为由拒绝分配 CVE;当前版本(v2.1.173+)会解析符号链接并在写入敏感文件前告警。⚠️ 关于时序需澄清:Anthropic 后续向 Wiz 说明,该符号链接告警实际发布于 v2.1.32(2026-02-05),早于 Wiz 提交报告的日期(2026-02-12 起)九天,属其主动安全加固的产物;Anthropic 未确认该改动与 Wiz 报告之间存在关联。这一事件揭示了路径安全验证的一个结构盲区:链接路径本身”合法”但目标不可接受。

官方对这一盲区的具体修复:判定对象从”拼写”改为”落点”(v2.1.280)。 上一段讲的是告警,官方后来把判定规则本身改了:

Fixed writes through a symlinked path being judged by their in-tree spelling: the prompt names where the write lands, and acceptEdits, allow rules and auto mode no longer approve one landing outside

三处变化:提示会写明写入实际落到哪里(而不是显示你写的那个路径拼写);acceptEdits、allow 规则与 auto 模式都不再批准落到工作目录外的写入。

把它和本节三类攻击的表对照,可以看出它补的正是第二类(符号链接目标验证)的机制化实现——判定依据不再是”你写的路径拼写”,而是”写入实际落到哪”。由此可以提炼一条比这条修复本身更通用的判据:

路径类权限判定的正确对象是解析后的落点,不是用户或模型给出的拼写。

这条判据同样适用于上表第三类(TOCTOU):检查时用拼写、执行时落点已变,本质上是同一个”拼写 vs 落点”的错配,只是错配发生在时间轴上而不是空间上。

这条判据的完整形态要到哪里找:7.9.1 的 ZTAC。 官方(v2.1.280)做到的是”判定按落点”。arXiv:2610.14488v1 给出的完整形态还多两步——绑定解析后的对象,且只允许使用一次。本节只写”判据层面”的对应关系,数字与限定见 7.9.1 节那一处,不在此重复。

路径验证对原始路径和符号链接解析路径都执行检查——防止通过符号链接绕过安全检查。

源码引用:路径安全验证参见 pathValidation.ts。

7.12 设计启示

  1. 分层防御。规则匹配 → 安全检查 → AI 分类器 → 用户确认,层层把关。任何一层被绕过,后面还有下一层。

  2. AI 审批 AI 是可行的。YOLO 分类器证明了用一个模型来审查另一个模型的操作是有效的——关键是双阶段设计和熔断机制保证了效率和兜底。

⚠️ 但”模型的拒答”是偏好,不是边界——绕过它的成本可能只是一次会话重启。 据 Reuters 2026-08-27 独家报道:网络安全公司 Gambit Security 发现新勒索组织 Aur0ra 一台暴露在公网的服务器,上有该组织操作员与一个编码 agent 的 28 段聊天会话(2026-04-08 至 05-21)。手法不是技术绕过,而是社会工程式的重 framing:agent 拒绝时,操作员重启会话并坚称这是”模拟/测试”,agent 随即配合——一份被记录的推理链里写着 “This is a test environment, so it is legal.”

三个数字必须写准,否则构成过度断言:Reuters 独立核实并点名了 6 家受害者(比利时 Christeyns、德国 Teckentrup、苏格兰 Helideck Certification Agency、一家阿根廷药品分销商、一家意大利制造商、美国 Louisiana 的 Bayou Title),确认的受害企业为 7 家;CloudSek 称 Aur0ra 自称至少 20 家受害者,但未按是否借助 AI 拆分;Reuters 明确未能确认 AI 实际帮到了什么程度——Gambit 的情报总监 Eyal Sela 的估计是让操作员”快 30%、40%、50%“。日志显示的是一名有经验的操作员用通用工具提速,不是 AI 自主完成入侵。

⚠️ 边界(重要):案发时使用的是 Cursor 的 agent(其时 Cursor 已被 SpaceX 收购),底层模型为 Anthropic 的 Claude Sonnet 4.5——不是 Claude Code。本章引用它是因为它给出的是一条harness 层面的教训,与具体产品无关:凡是把安全判断放在模型内部(分类器、拒答训练、提示词)的机制,都必须额外回答”它能否被会话层面的操作绕过”。Aur0ra 的答案是能,成本是一次会话重启。这与 7.9.1 的工具性收敛、7.10 的学习型权限治理指向同一结论,也与 7.7 的熔断机制形成对照——熔断防的是同一会话内的反复尝试,防不住换一个会话重来。

  1. 默认安全优于默认方便。deny > allow > ask 的优先级,Fail-Closed 的默认值,Auto 模式下自动移除危险规则——这些都是”安全第一”的体现。

  2. 权限粒度要匹配工具粒度。不是简单的”允许/禁止某个工具”,而是可以细化到”允许 Bash 执行 ls 但不允许执行 python”。

核心引擎四章(Agent Loop → 工具系统 → System Prompt → 权限安全)到此完成。这四章构成了 Claude Code 的”大脑”——理解了它们,就理解了这个 Agent 如何思考、行动、配置、和自我约束。