RAG 管道安全:检索引入的非对称信任缺陷。
检索增强生成被当作一种"接地"技术来兜售,人们还常常默默假设它能用有来源的事实替代模型的猜测,从而降低提示词注入风险。事实更接近相反:RAG 开辟了一条新的、攻击者可影响的通道,直通提示词。本文点明这个核心架构缺陷,为防御者梳理 RAG 特有的主要风险类别,并给出一个具体的信任边界设计——讲攻击的概念,而非攻击的配方。
核心缺陷:非对称信任
每一个严肃的 RAG 部署都会校验并守护用户输入——长度限制、注入分类器、内容策略,有时是一整套审核栈。然后它从向量库检索文档,把它们拼进同一个提示词,却没有可比的审查,因为语料库是"我们的数据",因而"可信"。这个非对称就是缺陷。用户输入和检索上下文进入同一个 token 流,模型以相同方式读取它们,但只有一侧被当作对抗性来对待。
这与 prompt-injection 的"指令与数据共用一个通道"是同一个教训,只是早了一层应用:语料库就是一种输入。如果你信任边界之外的任何人能影响最终进入索引的内容——公共网络爬取、用户上传、客户工单、可编辑 wiki、共享知识库——那么"检索到的"就不是"可信的"同义词。它是绕过了前门守卫的不可信输入。
RAG 安全里最重要的一句话:检索到的文本是不可信输入,其校验少于用户输入,受到的信任却多于用户输入。下面每一项防御都是缩小这个差距的手段。若你只记一句,记这句。
语料库投毒:少量文档,大型语料
防御者必须内化的反直觉结论:给 RAG 知识库投毒并不需要控制其中很大一部分。PoisonedRAG(Zou 等,USENIX Security 2025)表明,每个被针对的问题注入数量级为个位数的精心构造文档,就能把攻击成功率推到约 90%,即便语料库含有数百万文档。攻击者只需让其内容在某个特定查询上赢得检索,然后引导生成——语料库规模并不是它感觉上的那种防御。一个相关的可用性侧变体("Machine Against the RAG",用阻断文档进行干扰)则是压制正确答案而非替换它。
防御者不靠检视载荷来对抗这一点;他们在摄入边界对抗它,那里才是真正授予信任的地方:
- 来源溯源与来源白名单。记录每个分块的来源。把摄入限制在经审查的来源;把开放网络与用户贡献内容当作一个独立的、更低信任的层级——绝不与精选材料无声合并。
- 精选、签名的语料库。对高风险领域,对精选文档集签名,并在建索引时验证签名,使未签名或被篡改的文档无法进入。
- 摄入时的内容净化。在嵌入之前剥离或中和指令样式与隐藏内容(HTML 注释、零宽字符、屏外文本),而非在检索之后。
- 对索引的异常/离群检测。为在目标查询上赢得检索而构造的文档常表现为嵌入离群点,或呈现重复簇模式。监控索引,而不仅是输入。
"这是个百万文档语料库,几个坏文件不可能要紧"——正是这项研究所驳斥的直觉。每问题投毒成功率大致与语料库规模无关。规划摄入控制时,要把小规模、有针对性的注入当作预期情形。
经检索内容的间接注入
RAG 不消除提示词注入;它把间接变体工业化了。OWASP GenAI 把提示词注入列为 LLM01:2025,即首位风险,而检索到的文档是一等的注入载体:恶意指令存在于智能体获取并信任的一个看似无害的来源里,恰是 prompt-injection 中的间接模式——只是现在由一次相似度匹配自动触发,而非由攻击者与智能体对话触发。
CVE-2025-32711("EchoLeak",已由 Microsoft 修补,CVSS 9.3)是这一类别的标准概念示例:一条零点击链,经 RAG 管道摄入的不可信内容使助手浮现并外泄用户上下文中的数据,无需任何用户操作。我们只在类别层面引用它——不可信摄入内容把一个有特权的、检索接地的模型引向数据外流——因为它把 prompt-injection 与 data-exfiltration-risks 复合在一起:注入是入口,一个自动获取的输出通道是出口。修复空间是结构性的,不是一个措辞过滤器。
嵌入反演:存储本身会泄露
一个新兴的机密性风险存在于提示词之下,在向量库里。嵌入不是单向哈希;关于嵌入反演的研究表明,可以从存储的向量中重建出相当比例的原始文本。因此向量数据库不是"匿名化"敏感内容的安全场所——它是其有损但部分可逆的副本,且访问控制常弱于它被复制自的源系统。
- 向量库上的访问控制至少与源数据上的一样严格;不要让 RAG 成为绕过行级或文档级权限的侧门。
- 对索引及其载荷做静态与传输中的加密。
- 最小化嵌入中的敏感文本。在高敏感字段被嵌入之前就脱敏、令牌化或排除它们;最便宜可防的泄露,是你从未向量化的数据。
- 按租户/按用户分区,使一次检索绝不会跨越隔离边界。
防御设计:把检索文本视为不可信
统一的做法是把检索中那份隐含的信任变得显式且有界。具体而言:
- 默认不可信。对检索分块施加与对用户输入相同的对抗性假设——这些检查住在哪里见
guardrails。 - 结构化、带引号的上下文边界。把检索内容包进清晰分隔、带标签的块中。这是软控制(载荷可声称闭合该块),值得作为纵深防御去做,绝非你所依赖的边界。
- 下游工具最小权限。一次成功的检索注入造成的损害,受限于接地模型随后能做什么;按
agentic-threat-model,把那个集合保持到最小。 - 输出过滤与向用户呈现来源。扫描生成输出中的外泄标记;向用户展示是哪些来源支撑了答案,使被投毒的来源可见而非不可见。
- 监控。对检索离群点、首次出现却赢得检索的来源,以及"先回答后出口"的工具序列发出告警。
一个最小的信任边界包装器使这份非对称在代码里无法被遗忘:
# Defensive shape: retrieved chunks cross an explicit boundary, # not an implicit one. Conceptual, not a drop-in library. def ground(query, store): chunks = store.search(query, tenant=current_tenant()) # scoped retrieval safe = [] for c in chunks: if not provenance_allowed(c.source): # source allowlist / tier continue c = sanitize(c.text) # strip hidden / instruction-like safe.append(wrap_untrusted(c, src=c.source)) # labeled, quoted block # planner sees only delimited UNTRUSTED context + cited sources; # tools downstream are least-privilege regardless of content. return build_prompt(query, untrusted=safe, cite=True)
RAG 安全不是一门拴在检索上的新学科——它就是已有的智能体安全学科,被应用到一个团队忘了它是输入的通道上。请把它与 prompt-injection、data-exfiltration-risks、agentic-threat-model 和 guardrails 并读;控制相同,入口是索引。
它减少了一种失败模式(无来源的捏造),同时增加了另一种(攻击者来源的"事实")。一个有接地的错误答案可能比无接地的更危险,因为它带着引用和用户的信任而来。接地在良性输入上提升准确性;它不会把语料库变成可信通道。两个性质同时成立——为第二个做设计。
很少如此。"内部"通常仍意味着从工单系统、共享 wiki、上传文档、邮件和 CRM 备注摄入——它们全都可被你信任边界之外的人、流程或客户写入,有时还可被一个只需提交一张工单的攻击者写入。问题不是"它是内部的吗?"而是"谁能让一份文档进入索引?"答出这个,溯源与来源分层就随之而来。