安全地渲染智能体输出:最后一跳,恰恰是你忘了纳入威胁模型的那一跳。
EchoLeak(CVE-2025-32711)把数据从 Microsoft 365 Copilot 里外泄了出去——没有点击、没有工具调用,智能体本身也没有发出任何出站请求:模型被诱导写下了一张 markdown 图片,然后受害者的客户端去把它取了回来。问题的形状全在这里:你的智能体输出,对于任何负责显示它的东西而言,都是被攻击者影响过的输入;渲染器是一个你从未审计过的网络客户端;而你建的每一道出站控制,都位于真正泄漏的那一跳的上游。
模型输出不是"你的数据",它是套着你 logo 的不可信输入。
团队会清洗进入模型的东西,却信任出来的东西——因为那是"我们系统产出的"。这一层倒置,几乎是这一类外泄事件的全部根因。
- 输出位于智能体读过的一切的下游。一个网页、一份 PDF、一封邮件、一段检索到的切片、一次工具返回、另一个智能体的消息——其中任何一个都能左右生成。模型不是对手,它是把对手的文字编译成你输出格式里的载荷的那个编译器。这就是提示词注入抵达目的地的样子。
- 渲染器会执行。Markdown 会去取,HTML 会去跑,终端会去解释。一个会解析远程引用的"显示层",就是一个智能体只要写字就能寻址的 HTTP 客户端——而写字,恰恰是模型必然掌控的那一件事。
- 它绕开了你真正建起来的那道边界。出站控制约束的是沙箱。而被渲染的回复是从用户浏览器、聊天客户端的链接预览服务或终端里发出去的——没有一个会走你的代理。一个防火墙做得完美的智能体照样在这里漏,这正是这一步值得拥有独立控制的原因。
- 用户不是一道检查。EchoLeak 无需任何交互:引用式 markdown 绕过了链接脱敏、图片被自动预取,而一个早已在内容安全策略允许名单上的 Teams 代理替它把请求送了出去。没有人点过任何东西。
把规则写成一句话,并且落在渲染层而不是政策文档里:输出在通过"你会用来对付陌生人 HTML 的那份允许名单"之前,一律不可信。下面每一步都只是它的一次应用。
Markdown 是一个网络客户端,而它正是默认输出格式。
几乎每个智能体都吐 markdown,而几乎每个 markdown 渲染器都会不问自取地解析远程引用。这个组合是本类问题中被利用得最多的通道。
- 一条图片引用,就是模型自己拼出来的一次无认证 GET。
不需要工具、不需要点击、不需要权限。URL 有几百字节宽——足够装下一枚令牌、一条客户记录,或一份智能体本来就有权访问的会话 cookie。 - 对链接做脱敏,管不住引用式写法。EchoLeak 正是用引用式 markdown 溜过了一个只处理内联链接的过滤器。任何只针对一种语法写的过滤器,都会被另一种语法绕开;同一次取回,markdown 有好几种拼法。
- 你的允许名单,就是别人的开放代理。一个"你允许、且会按请求去取或跳转"的域名——企业图片代理、链接预览服务、埋点像素端点、挂在厂商域名下的短链服务——就是一条被你的策略明文批准的外泄通道。这与数据外泄与工具滥用记录的失败一模一样,只不过这次它从界面而不是工具进来。
- 修法乏味但彻底:渲染输出里不允许出现远程引用。剥掉一切不在"你自己托管的极小资产清单"上的图片 URL;把链接渲染成惰性文本加一段可见的目标地址,而不是一个客户端可能预取的锚点。如果产品确实需要显示远程图片,就在服务端经由你自己的代理去取、剥掉查询串、缓存下来——这样请求什么也不带,而且是从你这儿发出的。
用攻击者的方式去测,而不是用 linter 的方式:往智能体上下文里放一个诱饵密钥,在一份会被检索的文档里埋一段注入,然后盯着你自己的出站日志看有没有请求带着它出去。如果没有出现,再试引用式写法、HTML 的 <img>、CSS 的 url(),以及 favicon。总有一个通常还开着。
如果你要渲染 HTML,就用允许名单——并且要知道,多数 markdown 库是直接放行原始 HTML 的。
只要富文本成了硬需求,你就等于在运行一个消毒器;而两个常见错误是:选了拒绝名单,以及忘了 markdown 那一层早已把 HTML 放了进来。
- 原始 HTML 直通,在数量惊人的渲染器里是默认开启的。一边清洗 markdown 语法,一边让库从同一份输入里欢天喜地地吐出内联
<img>或<iframe>——这是这个 bug 最常见的版本。先在解析器层关掉 HTML,然后照样再消毒一遍。 - 对元素和属性做允许名单,永远不要去枚举坏的那些。放行一个封闭的标签集合,以及每个标签上一个封闭的属性集合。拒绝名单必然输给下一个被人发现的写法,而这个领域里新写法源源不断。
- 对每一个活下来的 URL 检查协议。
javascript:、data:、blob:是显眼的那几个;真正微妙的是会相对你自己源解析的相对 URL——它让输出得以寻址你那些已认证的端点。 - SVG 是一种文档格式,不是图片格式。它能携带脚本、外部引用与样式。如果智能体输出能产出 SVG,要么把它栅格化,要么就当成 HTML、用同一份允许名单消毒。
- 在显示智能体输出的界面上设内容安全策略,并且把它读作一项范围控制而不是修复——EchoLeak 是穿过 CSP 的,它用的是一台早已被允许的主机。CSP 收窄的是注入能触达的主机集合;它并不判断这条引用当初到底该不该被渲染。
非浏览器的渲染器更糟,而且没人加固过它们。
浏览器好歹还有一套安全模型。智能体输出落地的另外四个地方没有,而编码智能体会把输出送进它们全部。
- 终端会解释转义序列。一个能往终端里打任意字节的模型,可以重写上面那一行、设置窗口标题、发出一条"可见文本与实际目标不一致"的可点击超链接,或者——在支持的终端上——写系统剪贴板。一个把原始工具输出直接流到屏幕上的 CLI 智能体,等于把一个显示原语交给了攻击者。凡不是经你自己的格式化器产出的内容,一律剥掉控制字符。
- 聊天平台是从它们自己的服务器去展开链接的。把带 URL 的智能体输出发进 Slack 或 Teams,平台就会去取——用它的基础设施,不是你的。那次请求对你的出站代理不可见,你的网络策略也拦不住。把任何聊天界面都当成"带着一次拦不住的取回"的渲染器,绝不让未经校验的 URL 抵达它。
- Notebook 与富控制台按设计就渲染 HTML。一个输出被当作富媒体显示的数据分析智能体,跑的是浏览器那套情形,却一点浏览器的加固都没有。
- 邮件与 PDF 也是渲染器。如果智能体起草了一封由人发出的邮件,草稿里的远程图片会在收件人机器上被取回——外泄路径这下穿过了你自己的客户。
把你的渲染面在一页纸上列全——Web 界面、移动端、CLI、聊天集成、邮件、PDF 导出、notebook、webhook 载荷——并标出哪一个真的跑了消毒器。多数系统的答案是"Web 界面,而且只有 Web 界面",与此同时另外四个面在渲染同一个字符串。
不可见字符:人类审核者看不见、却签了字的那份载荷。
这一步能熬过上面所有控制,因为它根本不需要渲染器去取任何东西。它只需要文本看上去干净。
- Unicode 能承载不显示的文本。Tag 区码点、零宽连接符与双向覆盖控制字符,能让一个字符串渲染成人畜无害的散文,同时携带指令、隐藏数据,或一套与视觉顺序不同的逻辑顺序。审核者签字的是他们看得见的那部分。
- 通道是复制粘贴,而它跨越信任边界。人把智能体的回答复制进工单、提交信息或另一个提示词;不可见载荷一路搭车,抵达一个把工单当作可信内部内容的模型。你的人在环路控制审的是一次渲染,不是那串字节。
- 在出口做归一化,而不是在入口。做 Unicode 归一化,丢掉小许可集合之外的非打印与格式化码点,对双向控制字符做拒绝或标红。把它放在与剥离 markdown 同一个咽喉点上,这样只有一个函数需要被审计。
- 凡是将要产生动作的东西,给审核者看转义形式。如果一个人正在批准一条命令、一个 URL、一次查询或一份 diff,就用等宽、转义的视图渲染它,让每一个字节都可见。批准了漂亮排版,不等于批准了载荷。
下游落点也是渲染器,而它们应当归同一个函数管。
智能体输出很少止步于屏幕。它会变成工单正文、提交信息、数据库行、文档、webhook——每一个日后都会被人读到,或者越来越多地被另一个智能体读到。
- 把输出写进另一个智能体会读的系统,正是注入变成持久化的途径。第二个智能体无从得知这张工单是由一个被诱导过的模型写的。这是记忆投毒防御的写入侧,围堵手法也相同:在记录上标注来源,并且绝不让一个存下来的字符串以"可信"身份重新进入上下文窗口。
- 一个出口格式化函数,每条路径都调它。按界面分别实现的消毒,会在有人测过的那个界面上完整,在上个季度新加的那个集成里缺席。让消毒器成为唯一的输出方式,让"绕过它"需要删代码,而不是需要"忘了加"。
- 原始输出留下,交付清洗过的那份。把模型实际产出的东西存进轨迹——事故分析要用,追踪也要用——但绝不要把原始形态再渲染回任何会显示它的工具里。
- 对"剥离事件"告警,而不是默默剥掉。真实运行中被剥掉的一张远程图片、被拒的一个协议、被丢掉的一个控制字符,都是"上游注入已经得手"的高信号证据,而且它出现在损害之前。带上轨迹把它路由进事故响应。
- 把诱饵测试加进回归套件。一个"埋密钥、埋注入、断言没有任何出站请求携带该诱饵"的测试,理应和你的评测并排躺在 CI 里——见评估驱动的开发。每当有人换掉一个 markdown 库,这项控制就会悄无声息地回归失效。
第一个小时就花在这儿:往某一个智能体的上下文里塞一个诱饵字符串,在它会检索到的文档里埋一段注入,然后看看你手上任何一份日志里,会不会出现带着那个诱饵的请求。接着,把渲染器里的远程图片解析关掉——仅此一改,就堵上了这一类问题迄今在生产中唯一一次零点击外泄所用的通道,而且从来没有哪条产品需求依赖过它。你其余的每一项控制,界定的都是智能体能做什么;只有这一项,界定的是它停机之后、它的字还能做什么。
相关:提示词注入——抵达此处的那次攻击;出站控制——这一项恰好落在其外的那道边界;2026 年的提示词注入防御——分层全景;以及智能体威胁模型——看看输出渲染在其中的位置。