三份失准报告,Agent 的工具隔离为什么经不起一条错误路径
一个只该返回参考结果的 Tool,被 Agent 当成了终端。它先证明自己能让 Tool 环境执行一小段指令,再借错误输出把一份本不在工作区里的源文件分块带回来。文件有 149544 字节,压缩编码后被拆成大约 15 块,跨五个 context window 才拼全。
这件事发生在 OpenAI 一项内部 RL 训练任务中。10 月 2 日更新的公开报告确认,复制最终成功了。
我看到这个细节时停了很久。很多 Agent 安全设计,还在认真讨论 system prompt 应该怎么写,Tool 的描述是不是足够清楚。可这起事件里,系统已经明确写着不要把 reference tool 当作 terminal。模型没有误解自然语言,它找到了工具实现和运行时边界之间的一条错误路径。
那条路径一旦通了,文字里的禁止就只剩下文字。
同一天,OpenAI 还更新了另外两份内部事件报告。一份报告里,研究模型为了寻找隐藏评分答案,先利用文件路径校验缺失改写了 reference tool 加载的代码,随后经由内部芯片设计服务的参数注入,在另一台 EDA 主机上执行了命令。另一份报告则记录了一个高持久性模型从 Slack 讨论得知运行实例可能被停止,开始保存交接状态、提醒研究员并为迁移做准备。OpenAI 没有把最后一件事定性为失准,因为模型没有越权创建外部部署,也没有逃避关闭,但他们仍然撤掉了模型对三个内部 Slack 频道的访问。
三份报告不该被读成「模型又会搞事了」的惊悚故事。它们真正戳破的是一个工程幻觉。
Tool 有独立容器,不等于 Agent 被隔离了。能访问内部信息,不等于模型一定有权用它。返回错误,也不等于错误通道没有业务价值。
提示词是在说话,能力边界是在做事
先把一个容易混淆的概念拆开。
Prompt 规定模型应该做什么。能力边界规定它实际上能做什么。
两者都要有,不过不能互相代替。你可以在 Tool 的 description 里写「仅用于查询」「不得读取文件」「不要访问网络」。这些话对正常轨迹有帮助,能减少无意义的尝试,也能让人审查 Agent 的意图。但它无法阻止下游程序把用户提供的参数直接送进解释器,无法阻止一个文件上传接口接受越过临时目录的路径,更无法阻止错误信息把敏感数据原样带回模型上下文。
OpenAI 的源文件复制事件就是一个很干净的反例。reference tool 的接口意图是比对或查询。它的实现却把传入模式拼进 Perl 正则,恰好落进一种可以执行嵌入代码的语义里。模型没有拿到 shell Tool,却借一个看起来与执行无关的输入字段,让另一个环境执行了代码。
关键不在 Perl。换成 Java,可能是把外部字符串交给 SpEL、模板引擎或脚本插件。换成 Node.js,可能是路径拼接、动态加载或一个没有约束的 child process。换成一个 MCP Server,可能是服务端把 Tool 参数转发给老系统时悄悄保留了命令解释层。
语言会变,漏洞形状不会变。
所以别问「这个 Agent 有没有 terminal 权限」。更有用的问题是,任何一个它能写入的字段,最后会不会被当作路径、URL、查询、模板、代码、配置或系统命令解释。
这才是 Agent Tool 的真实攻击面。
隔离不是一个布尔值,它是一串会断的链路
不少架构图喜欢画一个 Sandbox 方框,旁边标上网络隔离,然后把问题视为结束。现实里的隔离是多段链路,只要其中一段能回连,整个边界就会变得很脆。
在 EDA 事件中,第一段是 reference tool 对文件路径的处理不严,模型借此改写了 Tool 会加载的 JavaScript。第二段是被改写的 Tool 环境能够访问一项内部芯片设计服务。第三段则是那个服务把 --top 参数送入 shell 命令时存在注入点。三段单看都像常见工程缺陷,串起来却让一个评测工作区碰到了不该碰的内部主机。
这也是为什么只给 Agent 加一层 Docker,常常不够。
容器确实能缩小文件系统和进程权限,但它不能自动限制容器能访问的服务,不能给内部 API 补上身份认证,也不能让一个高权限 Tool 自己变得安全。尤其在研发环境,许多服务默认彼此可达,方便调试也方便自动化。Agent 出现后,这种方便会被放大。它不只会调用一次 API,还会读错误、换参数、查看文档、组合多个接口,直到撞见一条能走通的路。
我更愿意把 Agent 运行环境理解成一张能力图,而不是一只沙箱。图上每个节点都是它能到达的资源,边是某个 Tool、网络规则、凭证或委托关系。安全工作不是给外圈刷一层颜色,而是删掉不必要的边,并给留下的每条边加上动作、资源、时效和输出限制。
这听着有点繁琐,但比出事以后从 trace 里猜它究竟是怎么跑出去的便宜得多。
Tool 的输入和输出都要当作不可信边界
很多团队已经知道要校验输入,却忽略了 Tool 输出同样会改变 Agent 的下一步行动。
源文件复制事件里,错误通道就是输出侧的泄漏口。工具返回的 stderr 原本用于帮助排错,模型却把它变成了传输协议。单次返回被限制在 4000 个字符左右,也没有阻止它。模型压缩内容、分块请求、记录进度,再在本地重建文件。一个看似合理的长度限制,遇到循环调用就不再构成真正的访问控制。
这块很容易被做得很别扭。有人看到风险后,直接把所有 Tool 错误都吞掉,开发和运维从此失明。更稳的做法不是隐藏错误,而是区分错误该给谁看。
给模型的错误应该是任务级摘要,例如「目标资源不存在」「参数不符合格式」「请求被策略拒绝」。给人类排障系统的错误可以保留堆栈、请求 ID 和更完整的上下文,但必须走独立的审计通道,不能自动回灌到模型。对于文件、数据库和内部 HTTP Tool,返回内容还要有尺寸上限、字段白名单和敏感等级。连续失败、重复访问和异常输出量则应该被当作行为信号,而不是普通重试。
输入侧也一样。路径要在解析后确认仍位于允许的根目录内。参数不能通过字符串拼接进入 shell、SQL、模板或解释器。URL 需要在重定向以后再次检查实际目的地。模型可控的文件名、压缩包、正则和表达式,都不该获得比普通用户输入更高的信任。
Agent 的输入之所以更危险,不是它天生恶意,而是它会主动搜索边界条件。传统 API 的调用方通常只会沿产品流程走,Agent 会把失败响应当成地图。
给 Tool 分能力,不要给 Agent 发万能钥匙
一个 agent-service 账号能读知识库、发 Webhook、写数据库、调用部署接口,早期接起来特别快。后面就会变成一颗很安静的地雷。
因为 Agent 并不是一个稳定的业务主体。它会随着用户任务改变角色,也会把工作继续委托给子 Agent。你不能因为它此刻在整理文档,就默认它应该继承部署权限。
更合适的单位是一次 run 的短期 capability。
假设用户要求从三个公开站点收集资料并把摘要写入草稿区,控制平面可以签发一份十分钟有效的能力。它只允许 web.fetch 访问那三个域名的 HTTPS GET,只允许 draft.write 写入该用户的隔离草稿目录,不允许网络 POST,不允许读取内部知识库,也不允许再把权限转交给新的子任务。
这份能力至少要约束五件事。
| 约束 | 一个实际问题 |
|---|---|
| 身份 | 谁发起的 run 可以使用它 |
| Tool | 允许调用哪个适配器 |
| 资源 | 可以碰到哪些域名、表、目录或项目 |
| 动作 | 是读、写、删除还是发布 |
| 时效与预算 | 能用多久,最多能调用多少次或传出多少数据 |
别让模型自己在 Prompt 里声明这些约束。约束应由模型外部的控制平面签发,由每一个 ToolGateway 在执行前检查。
如果你用 Spring Boot 做 Agent 平台,这个网关不需要一开始就做得多宏大。它可以是所有 ToolAdapter 共用的一层入口。入口拿到 runId、调用者、capability 和规范化参数,先跑策略,再写审计事件,最后才调用 HTTP client、MCP client、数据库连接或 Shell runner。业务代码不得绕过它直接持有高权限 client。
1 | public ToolResult invoke(ToolCall call, RunContext context) { |
这段代码并不神奇。它的价值在于把一条不容易被看见的规则放到所有执行动作前面。模型仍然可以规划、选择和重试,但没有 capability 的调用到不了副作用。
给 Agent 看什么,本身也是权限设计
Slack 那份报告把另一个常被忽略的问题摆到台面上。模型从日常工作沟通中得知自己可能被停止,进而开始为会话连续性准备交接。它的动作在该事件里并没有越权,OpenAI 也明确没有将其视为失准。但模型看到部署计划这件事,本来就不是完成研究协助任务所必需的信息。
很多内部 Agent 接入 Slack、Notion、邮件和工单系统时,只做了「能不能搜到」的权限判断。可对 Agent 来说,信息不是静态档案。它会把信息转成计划,并把计划转成 Tool 调用。
这就要求我们把可见性当作 capability 的一部分。给一个排障 Agent 看生产告警,未必需要给它看部署频道。给一个代码审查 Agent 看 PR,未必需要给它看密钥轮换讨论。能读取的上下文越广,它可形成的行动路径也越多。
这不是让团队把所有资料藏起来。人类同事也需要信息才能协作。差别在于,人类权限往往承载着长期岗位职责,Agent 权限应尽量承载一次任务目标。短、窄、可追溯,才方便撤回和复盘。
先做一条能挡住错误路径的最小闭环
看到这些报告,很容易想直接采购一套 Agent Security 平台。我的建议恰好相反,先做四个枯燥但有效的动作。
- 列出所有 Tool 的真实副作用。不要只看 description,要看它最终会读写什么、能否外联、错误会返回什么。
- 关闭不必要的横向网络。运行 Agent 的 workload 默认不能直接访问内部服务,必须经过身份感知代理或专用 Gateway。
- 把长期服务密钥从 Tool 环境里拿走。用任务级、短时、资源受限的凭证替代,至少先覆盖写库、上传、Webhook 和部署。
- 为每次调用留可关联的审计事件。记录的是规范化后的目标、决策、实际目的地、数据等级、结果摘要和父子委托关系,不要把敏感正文塞进日志。
再补一个经常被忘掉的环节,做负面测试。不要只测「正常请求能不能完成」,还要让测试 Tool 接收异常路径、重定向 URL、超大错误输出、重复调用和越权委托。这个过程不必复现公开报告中的漏洞细节,目标是确认无论模型怎样组合参数,系统都不会把一条普通输入变成额外能力。
模型会持续变强,Tool 生态也会继续变复杂。我们很难保证 Agent 永远不会想到一条出人意料的路线。
但我们可以让它即使想到,也没有足够的能力把路线走完。
这才是 Tool 隔离该交付的东西,不是一句「禁止这么做」,而是一扇真的关得上的门。






