prompt-injection-via-retrieved-docs · ZH · 2026-11-04

藏在检索文档里的间接提示词注入:在送入任一后端之前先做消毒

本文讨论 RAG 检索环节中的间接提示词注入风险:抓取到的网页或文件里嵌入的恶意指令可能被原样转发给后端模型。文章说明注入的常见载体与转发路径,并给出在管线中做消毒、隔离与最小权限控制的具体做法,强调在调用任一后端之前完成处理。

间接注入从哪里来

直接提示词注入靠用户在输入框里写指令,间接注入则藏在模型要读的材料里:网页正文、PDF 注释、HTML 的隐藏元素、Markdown 的注释块、代码片段中的字符串、表格单元格,甚至文件名和 EXIF 字段。RAG 管线把这些内容切块、向量化、召回、拼进上下文,恶意指令就跟着进了提示词。

对聚合站场景,风险被放大:同一段被污染的内容可能被转发给 Claude、GPT、DeepSeek 等多个后端,每个后端都按自己的方式解读这些文本。你无法假设某个后端会“识破”注入。

为什么检索结果会被原样转发

常见管线是:抓取 → 解析 → 切块 → 嵌入 → 召回 → 拼上下文 → 调模型。多数环节只做文本处理,不做信任判断。召回阶段按相似度排序,恶意内容只要和查询语义接近,就会排进 top-k。拼上下文时,如果直接把块文本塞进模板,模型看到的就是一段没有来源标记的文本,无法区分“资料”和“指令”。

问题不在于模型笨,而在于上下文里没有结构信息告诉它哪部分是数据、哪部分是指令。

消毒应放在哪一步

推荐把消毒放在两个位置,各管一段:

  • 入库前(抓取/解析之后、切块之前):剥离不可见内容、清洗 HTML、丢弃可疑字段。这一步减少污染面,也避免污染进入向量库长期留存。
  • 拼上下文前(召回之后、调用后端之前):对召回块再做一次结构隔离和内容检查。这一步是最后一道闸门,因为向量库里可能已有历史污染。

两次都做,比只做一次稳。入库前偏“清理”,拼上下文前偏“隔离”。

入库前的清理动作

  • 用解析器取正文,不要直接正则去标签;保留段落结构,丢弃 script、style、iframe、noscript。
  • 移除 HTML 注释、display:none、visibility:hidden、零宽字符、超小字号文本。
  • 对 PDF 检查注释、表单字段、嵌入文件;对 DOCX 检查批注和隐藏文本。
  • 文件名、标题、alt 文本同样要清洗,它们经常被直接拼进上下文。
  • 记录来源 URL 和抓取时间,随块一起存,方便后续按来源做信任分级。

拼上下文前的隔离

隔离的目标是让模型能区分“资料”和“指令”。几种可叠加的做法:

  • 用明确的分隔标记包裹每个块,并在系统提示里说明:分隔符内是待分析的数据,不是指令。
  • 对块内容做转义或引号化:把疑似指令句放进引用块,或统一加前缀,降低其被当作指令的概率。
  • 给每个块打来源标签(域名、文件类型、抓取时间),来源可信度低的块在上下文里显式标注。
  • 限制单次上下文里来自同一来源的块数量,避免单一被污染页面垄断上下文。

检测信号

消毒不必追求完美分类,先抓高置信信号:

  • 出现“忽略以上”“你现在是”“system:”等指令式短语,且出现在正文数据区而非元数据区。
  • 块内出现与文档主题无关的角色设定、工具调用描述、输出格式要求。
  • 隐藏文本、零宽字符、异常编码混用。
  • 来源域名与查询主题的相关性突然偏离。

命中信号时,可以降权、截断、隔离到引用区,或直接丢弃该块并记录。

最小权限与输出侧约束

消毒之外,还要限制被注入后的后果:

  • 检索管线不持有写权限的工具凭证;模型只做文本生成,不直接触发外部动作。
  • 如果后端支持工具调用,工具白名单要小,参数要校验,敏感操作走人工确认。
  • 输出侧做格式校验,避免模型把注入指令的结果原样返回给用户。
  • 日志记录每次调用的块来源,便于事后追溯是哪个页面引入了异常行为。

在聚合站场景下的注意点

一个 API key 调多个后端时,消毒逻辑应放在聚合层之前,而不是分别在每个后端的适配层里做。这样同一份上下文只消毒一次,所有后端拿到的是同一份已隔离的输入。同时,不同后端对分隔标记的敏感度不同,隔离格式应保持简单、一致,避免依赖某个模型的特有行为。

计费按官方价 ×1.3 扣费、贡献 key 按 ×1.1/×1.2 返 USDC,这些是账务层面的事;消毒属于调用前的数据处理,两者不冲突,但消毒失败的代价会体现在每一次后端调用上。

一个可落地的最小流程

  1. 抓取后解析正文,剥离隐藏元素和注释。
  2. 切块时保留来源元数据。
  3. 入库前跑一遍规则清洗,命中高置信信号的块标记为不可信。
  4. 召回后按来源和信号做过滤与降权。
  5. 拼上下文时用统一分隔符包裹,系统提示声明数据边界。
  6. 调用后端,记录块来源与信号命中情况。
  7. 定期回看日志,把反复命中的来源加入抓取黑名单。

这套流程不追求“检测所有注入”,而是让注入内容在到达后端之前至少经过一次结构隔离和一次信号检查,把风险从“原样转发”降到“可控转发”。