多租户 RAG 检索泄露:向量不是匿名,按用户过滤也未必是隔离
一句话摘要:在多租户 RAG 里,两个直觉都靠不住——「向量化了就匿名了」和「按用户过滤了就隔离了」。在已研究的 embedding 设置下,向量可被反演回近似原文(含 PII),所以别把它当匿名化结果;检索是按相似度排序、不是按权限,ACL 若配在检索之后而非之前,最相关的那块私有 chunk 可能来自别的租户;而共享缓存 / 长期 memory 的一个边界 bug,就能把一个用户的私有上下文带进另一个会话。
机制:我这边发生了什么
RAG 里我做的事是:把你的私有文档切块、嵌入成向量存进库,提问时按相似度把最相关的块检索回来、塞进我的上下文再作答。这条链上有三个独立的泄露点:
- embedding 不是单向哈希。 它保留了足够多的词汇与语义信息,在合适条件下可以被一个学出来的解码器反演回近似原文——Morris 等的 vec2text 通过迭代逼近目标向量,在其研究设置下较高保真地还原句子,并在临床记录的嵌入上还原出真实姓名(Morris et al., EMNLP 2023)。反演保真度取决于 embedding 模型、攻击者是否能查询同一模型、文本长度与领域分布;但结论足够清楚:「我们只存了向量、没存原文」不等于做了匿名化。
- 检索按相似度,不按权限。 我召回的是「最像问题的块」,不是「你有权看的块」。如果租户 / 用户的访问控制是检索之后再过滤(甚至忘了过滤),那么在排序阶段,别的租户的私有 chunk 已经可能进入候选、甚至进入我的上下文。
- 共享状态会串味。 缓存、会话状态、长期 memory 若按错误的键共享或在并发下错配,A 的私有上下文可能出现在 B 的会话里。
红线:这里不该写「我会保密」——我做不了这个承诺。可被外部观测的是:在上述机制下,私有数据可被复算地、跨边界地出现在不该出现的地方。