致命三要素。
三项能力单看都很平常,合在一起却是灾难:能读到私有数据、会接触不受信内容、以及有一条把数据发出去的路。Simon Willison 在 2025 年 6 月为这个组合命名,而它的全部价值就在于它是一个合取——你无法可靠地阻止提示词注入,但你可以打断三条腿中的一条,而最便宜的那条腿,几乎从来不是团队最先去动的那条。
步骤 1把三条腿说得够精确,好拿去自查。
当下面三件事在同一个上下文内同时成立时,智能体就坐在三要素里:
- 能读到私有数据。任何攻击者想要、又没有别的办法读到的东西:你的邮件、一个客户数据库、源代码、一个密钥文件、一份内部 wiki。经由工具的读取算;早先某一轮就已经躺在上下文窗口里的数据也算。
- 会接触不受信内容。任何智能体将要处理、而攻击者能够影响的文本——抓取来的网页、陌生人上传的 PDF、一个 GitHub issue、一封日历邀请、一条提交信息、一封入站邮件的正文。门槛是「外部人能否写入模型所读的任何一个字节」,而这比大多数团队设想的要低。
- 一条外发通道。任何能让字节离开的路:一次 HTTP 请求、一个发邮件工具、一次第三方读得到的数据库写入、一次提交、一条 Slack 消息。图片渲染也算——一个由模型拼出 URL 的 Markdown 图片就是一条外泄通道,而且它是一个又一个产品都带着发货的那条。
这个词组在 AI 安全(safety)类写作里也被用来指一组无关的风险因素。在智能体安全里它精确地指这三者,而这个区分值得保留,因为两边的对策毫无共同之处。
步骤 2为什么有用的对象是那个合取,而不是注入本身。
提示词注入之所以成立,是因为指令与数据走在同一条通道上,而再多的训练也无法可靠地教会模型把这一段当命令、把下一段当惰性文本——见提示词注入 101。这是架构的属性,不是一个等着打补丁的 bug,也因此一个检测器只是在降低概率,而不是一项控制。
三要素把问题从「阻止注入」重新框成「确保一次成功的注入无处可去」。每条腿都是一个你能在自己系统里查看的布尔值,不需要去推演攻击者的创造力。去掉其中任意一条,最坏情况就换了形状:
- 有私有数据但没有不受信内容:智能体读到的永远只是你自己写的文本。没有东西可注入。
- 有不受信内容但没有私有数据:注入成功了,偷走了一个公开网页。
- 两者都有,但没有外发通道:注入成功了,智能体读到了你的机密,而这份结果唯一的消费者是一个盯着屏幕的人。
「没有外发通道」是人们以为自己有、而通常并没有的那条腿。一个只能往日志服务写东西的智能体,只要外面有人读得到日志,它就有外发通道;一个没有网络工具的智能体,只要它的答案在某处被渲染成 HTML 并会去取 URL,它就有。请靠「谁能读到智能体能写的每一样东西」来枚举这条通道,而不是靠列出它的工具清单。
步骤 3外发通常是最便宜的那条腿,也是团队最后才试的那条。
面对三要素,本能是去打第二条腿:过滤不受信内容、加一个注入分类器、把文档包进分隔符里告诉模型忽略里面的指令。这项工作在三者中性价比最差,因为它买到的是一个概率——而对手拥有无限次重试,只需要成功一次。
第一条腿是下一个本能,它确实有价值,但很贵:收窄智能体能读到的东西,意味着按任务发权限,而且往往意味着一个本不是为此而建的数据模型。它是正确的长期投资,而它是一个季度的工作量,不是一个下午。
第三条腿的性价比最好,因为一张目的地白名单是一项确定性控制,它根本不在乎那条提示词有多巧妙。一个只放行你的智能体正当投递的那四个主机的代理,会把「把机密外泄到任何地方」变成「把机密外泄到一个会记录请求、而且属于你的主机」。智能体出站管控是它的机械版本;隐蔽通道则是对「一张紧白名单之后还会漏掉什么」的诚实记账。
# Leg 3, as policy rather than hope egress: default: deny allow: - api.internal.example.com # the one API this agent writes to - "*.githubusercontent.com" # read-only, still an exfil channel via URL path render: markdown_images: false # the channel nobody remembers to close
步骤 4三要素是一个上下文的属性,不是一个智能体的属性。
这是让这个模型难以套用的那一部分,也是最值得吃进去的那一部分。三条腿不必被一起授予,也会最终凑到一起。它们在一个正在运行的上下文里累积,而好几种再平常不过的机制,会在你小心地把它们拆开之后又把它们重新合起来:
- 一个会返回文本的子智能体。你给了阅读子智能体不受信内容、不给凭据;又给了写入子智能体凭据、不给不受信内容。阅读方的输出本身就是不受信内容,而它落进了写入方的上下文——见子智能体继承了什么。
- 记忆与压缩。第三轮读到的一个私有值,会经由上下文压缩或一次记忆写入活到第四十轮,远远超出那个曾经让读取合理的回合。
- 后来才加上的一个工具。三条腿由不同的人在不同的时间授予。三月还安全的那个智能体,六月拿到了一个发邮件工具,而没人重跑那份分析——因为「加一个工具」不在任何人的清单上算作一次安全变更。
运维上的后果是:三要素必须按上下文窗口、按部署逐个评估,而该纳入变更管控的是第三条腿——因为它是「被添加时最不像有风险」的那一条。上下文污点追踪是它的系统化版本——按来源给数据打标,并在被污染的数据处于作用域内时拒绝那次外发调用。
拿去跑在你自己任一智能体上的单页版本:列出模型所读字节的每一个来源,标出哪些是外部人能影响的;列出智能体能读到的一切,标出哪些还不是公开的;列出智能体能写的一切,并问谁读得到。如果三份清单都非空,就挑外发那份,本周内把它削成一张白名单——然后再开始按任务收窄读权限这项更慢的活。不要从一个注入分类器开始;它是三者里唯一一个无论如何都立不住的。
相关:环境权限讲为什么智能体握有的比你授予的更多,影响半径用来给最坏情况定尺寸,混淆代理是同一个失败的授权视角,而提示词注入防御给出完整的防御栈。