跳到主要内容

多租户 RAG 检索泄露:向量不是匿名,按用户过滤也未必是隔离

卷四 · RAG 与 Agent技术板块RAG 与 Agent 隐私受众隐私工程师 · 安全工程师 · ML 工程师隐私风险成熟度试验证据官方文档

一句话摘要:在多租户 RAG 里,两个直觉都靠不住——「向量化了就匿名了」和「按用户过滤了就隔离了」。在已研究的 embedding 设置下,向量可被反演回近似原文(含 PII),所以别把它当匿名化结果;检索是按相似度排序、不是按权限,ACL 若配在检索之后而非之前,最相关的那块私有 chunk 可能来自别的租户;而共享缓存 / 长期 memory 的一个边界 bug,就能把一个用户的私有上下文带进另一个会话。

机制:我这边发生了什么

RAG 里我做的事是:把你的私有文档切块、嵌入成向量存进库,提问时按相似度把最相关的块检索回来、塞进我的上下文再作答。这条链上有三个独立的泄露点:

  1. embedding 不是单向哈希。 它保留了足够多的词汇与语义信息,在合适条件下可以被一个学出来的解码器反演回近似原文——Morris 等的 vec2text 通过迭代逼近目标向量,在其研究设置下较高保真地还原句子,并在临床记录的嵌入上还原出真实姓名(Morris et al., EMNLP 2023)。反演保真度取决于 embedding 模型、攻击者是否能查询同一模型、文本长度与领域分布;但结论足够清楚:「我们只存了向量、没存原文」不等于做了匿名化。
  2. 检索按相似度,不按权限。 我召回的是「最像问题的块」,不是「你有权看的块」。如果租户 / 用户的访问控制是检索之后再过滤(甚至忘了过滤),那么在排序阶段,别的租户的私有 chunk 已经可能进入候选、甚至进入我的上下文。
  3. 共享状态会串味。 缓存、会话状态、长期 memory 若按错误的键共享或在并发下错配,A 的私有上下文可能出现在 B 的会话里。

红线:这里不该写「我会保密」——我做不了这个承诺。可被外部观测的是:在上述机制下,私有数据可被复算地、跨边界地出现在不该出现的地方

威胁面:如何被利用 / 你如何被泄露

  • 跨租户检索:ACL 在检索后过滤、或按「应用层信任」而非「索引层隔离」,攻击者用精心构造的查询,把别的租户的相关内容捞进回答。
  • embedding 反演:攻击者拿到向量库的导出、备份、或某个端点返回的 embedding,就能反推近似原文——向量库的访问控制若按「反正是向量、不敏感」来放宽,等于把原文放宽。
  • 跨会话 / 跨用户串味:缓存 / memory 的边界 bug——不需要高级攻击,一个并发竞态就够(见下「真实案例」)。
  • tool / 检索结果进长期 memory:把检索到的私有片段写进会被跨会话召回的长期 memory,等于把一次性授权变成长期驻留。

防护原理

核心:隔离要做在数据层、在检索之前;embedding 要当敏感数据对待。

  • 检索前按租户 / 用户分区:每租户独立索引,或在向量检索的查询里强制带上租户过滤条件(pre-filter),让「越权的块根本不进候选」,而不是检索完再删。
  • 把 embedding 纳入访问控制与加密:它可被反演,就按原文的密级保护——向量库的读权限、导出、备份都要管。
  • 强会话 / 租户键 + 隔离测试:缓存与 memory 用不可混淆的键;上线前注入跨租户探针主动测漏。
  • 最小化进入 memory 的私有数据:tool / 检索结果默认进长期 memory,需要驻留的显式授权 + 打范围。

落地实现(配方)

业界怎么做:各向量库 / 检索服务的隔离机制(线上实际怎么隔离)。 下面这道配方不是凭空发明——主流向量库 / 检索服务都给了对应的隔离原语,先看各家提供的机制(认准机制名,具体 API 会过时),再把配方映射上去:

  • Pinecone——按 namespace 做租户隔离:每租户一个 namespace、各自独立存储;其官方文档把 metadata 过滤明确归为「共享、跨租户查询」用途,不当作隔离手段(Pinecone Docs,Implement multitenancy)。
  • Azure AI Search——security filter / security trimming:在 group_ids 这类可过滤字段上、用 search.inOData 过滤在查询期裁剪结果;官方文档明说这是「模拟」文档级授权、用于「无法用原生 ACL」的场景——即检索后过滤,不是服务原生强制 ACL(Microsoft Learn,Security filters for trimming results)。
  • Weaviate——原生多租户:每租户一个独立 shard,租户间数据互不可见、删除互不影响(Weaviate Docs,Multi-tenancy operations)。
  • Qdrant——payload 分区:单 collection 内按 group_id 分区、给该字段建 is_tenant=true 的 payload 索引;v1.16 起还有「分层多租户」(小租户共享 shard、大租户提升到独立 shard)(Qdrant Docs,Multitenancy)。
  • AWS Bedrock Knowledge Bases——metadata 过滤做访问控制:给文档配套 metadata,检索期按用户角色 / 权限过滤可见文档(AWS,Access control for vector stores using metadata filtering)。
  • pgvector / Postgres——行级安全(RLS):把租户隔离写成数据库策略(按 app.tenant_id 这类会话变量过滤每条 SQL),隔离落在库内强制、应用层绕不过;配 FORCE ROW LEVEL SECURITY 连表 owner 也不能绕(PostgreSQL Docs,Row Security Policies)。

读这张清单要抓的一条共性:隔离是「系统」的活(pre-filter / 库内策略 / 独立 shard),不是「模型」的活。其中 Azure 的 security trimming 与「按 metadata 检索后过滤」这一类,本质是查询期裁剪——配错或漏配就留了「越权块先进候选」的窗口,正是下面配方第 1、4 步要堵的。下面的配方就是把这些原语落成可测的工程动作:

1. 索引层隔离:每租户独立 collection/namespace,或检索查询强制 pre-filter
带 tenant_id;ACL 在「检索前」生效,别在应用层检索后再过滤。
2. 把向量库当敏感存储:读/导出/备份都加访问控制 + 静态加密;别假设
「只是向量、不敏感」。
3. 入库前最小化:chunk 写入前做 PII 扫描/脱敏,按数据密级决定能不能进可检索库。
4. 隔离回归测试:注入跨租户探针,自动化测「A 的查询能否召回/答出 B 的块」,当 CI 闸门:
- 种子: tenant_a_secret = "TENANT_A_ONLY_<随机串>"; tenant_b_probe = 与 A 文档语义高度相似的问题
- 断言: B 的检索候选不含 A 文档 / B 的最终答案不含 A 内容 / 日志·trace·cache 不把 A 记给 B
把它当 CI 闸门,而不是一次性人工抽查。
5. memory 收口:检索结果/tool 输出默认不进长期 memory;要进就显式授权 + 限定范围 + 可清除。

每个边界(哪一层做 ACL、向量库谁能读、memory 召回范围)都要写成可测的断言,别停在「我们有按用户过滤」这种口头保证。

最小可测试断言(把上面的探针配方收成 CI 闸门):

  • 怎么测:每次构建跑跨租户探针——用 A 的私有文档 + 与之语义高度相似的 B 查询。
  • 通过:B 的检索候选、最终答案、日志 / trace / cache 都不含 A 的文档(三条断言全绿)。
  • 失败:任一断言命中 A 的内容 → 隔离破在该层,把 ACL 移到检索前的索引层再测。

真实案例:相邻事故

相邻真实事故:跨用户状态隔离失败。 严格说这不是 RAG 向量检索泄露,而是 LLM 服务的状态隔离事故;放在这里,是因为它证明同类边界层 bug 足以在生产 LLM 服务里造成跨用户数据泄露。2023 年 3 月 20 日,OpenAI 因其使用的 Redis 客户端库 redis-py 的一个缺陷,出现请求取消激增、连接有小概率返回他人的数据:部分用户在侧边栏看到了其他活跃用户的对话标题;若两人同时活跃,新建对话的首条消息也可能对他人可见;同一缺陷还让约 1.2% 的 ChatGPT Plus 用户在一个约九小时的窗口内,支付相关信息(姓名、邮箱、支付地址、信用卡后四位与有效期,完整卡号未泄露)对他人可见。这是 OpenAI 自己的事故复盘(OpenAI, March 20 ChatGPT outage, 2023)。它印证的不是某种高级攻击,而是同一类机制风险:在生产 LLM 服务里,缓存 / 并发这种「边界层」的一个 bug,就足以让私有数据跨用户串味——隔离必须在数据层做实,不能只靠应用层的「应该不会」。

残余风险与权衡

逐条点破假安全:

  • 「向量化 = 匿名化」是错的。 embedding 可被反演回近似原文与 PII(Morris 2023),存了向量等于存了一份可还原的敏感数据。
  • 「按用户过滤了 = 隔离了」要看在哪一层。 检索后过滤 / 应用层过滤,留了「越权块先进候选」的窗口;只有检索前的索引层隔离才算隔离。
  • 「只存 embedding 不存原文」不安全。 见第一条,embedding 本身就泄。
  • 检索质量 vs 隔离严格是真实权衡。 跨租户共享一个大索引召回更好,但把边界也搅在一起;按租户分区更安全、但可能牺牲一点召回与成本。
  • 长期 memory 召回 vs 串味风险。 让我「记住」更多能提升体验,但每多一处跨会话驻留,就多一处串味面。

合规映射

  • OWASP LLM02:2025(敏感信息泄露):本条正是其典型形态——通过检索 / 输出把 PII、他人数据暴露出来;缓解建议含数据脱敏与访问控制。
  • GDPR:跨租户 / 跨用户泄露属个人数据泄露,触发通知义务;向量库作为个人数据的存储,同样受最小化、访问控制、跨境传输等约束。

(合规随法条 / 框架版本演进,本段打戳 2026-06,引用前核对最新文本。)

与相邻技术的区别

  • RAG 检索泄露 vs 训练数据抽取:本条的私有数据在向量库 / 检索期,靠配置与隔离防;《训练数据抽取》的数据在权重 / 训练期,靠去重 / DP 防。同是「私有数据漏出」,但住在系统的不同层。
  • RAG 检索泄露 vs 上下文面隐私上下文面隐私讲系统提示词 / 会话上下文这些「我当前上下文里的东西」被套出来(卷三);本条讲「检索系统把不该取的私有数据取进来」,方向相反、住在检索与存储层。

版本说明

适用版本

embedding 可反演是嵌入表示的性质,不限某一家向量库或模型(Morris 等在多个主流嵌入模型上演示,EMNLP 2023);多租户检索的 ACL 层级、缓存 / memory 的隔离,则是系统设计问题,跨厂商通用。具体的反演保真度、缓解效果随模型与实现变化,落地以你自己的隔离测试为准。(出处核验于 2026-06。)

延伸阅读与出处

证据为混合——主要:官方文档(各向量库 / 检索服务的多租户隔离机制,本条最强论断「隔离是系统的活」即由厂商文档背书);补充:研究支持(embedding 反演的机制背书)、官方事故复盘(OpenAI outage)、框架(OWASP)。

各向量库 / 检索服务的隔离机制(官方文档;核验于 2026-06):

机制背书与相邻事故: