检索鉴权不能漏掉缓存和关键词分支
混合检索的关键词分支、提前返回的缓存以及按 ID 读取等路径,都可能绕过权限控制。文章建议服务端从已验证身份推导读取范围,将权限过滤覆盖所有内容返回路径,并以无权限用户进行针对性探测。
AI 深度解读
给 RAG 的向量搜索加上权限过滤,仍然可能泄露数据。关键词搜索、缓存、按 ID 查询,甚至一条指向私有记录的关联,都可能成为另一条入口。原文的结论很明确:凡是能返回存储内容的路径,都必须执行权限检查,读取范围要由服务端从已验证身份中确定。
这篇文章的价值在于把权限问题拆成了可检查的工程细节。不过,它没有实测任何产品:引用的漏洞修复、文档规则、论文结果和作者推断,需要分开看。
搜索加了过滤,为什么还会漏
混合搜索通常有两路:向量分支按语义找内容,词法分支通过 BM25 或 PostgreSQL 全文搜索匹配词语。只给向量分支加租户过滤,关键词分支就可能把受限文档送进合并结果。
相关性决定内容有没有用,授权决定内容能不能被读取。
排序融合不能替代权限检查。原文给出两种统一约束的办法。
一种是把规则放到数据库。PostgreSQL 启用行级安全策略后,普通访问受策略约束,没有策略时默认拒绝。但表所有者通常不受限制,除非设置 FORCE ROW LEVEL SECURITY;超级用户和带 BYPASSRLS 的角色始终绕过检查。应用连接用了什么角色,直接决定策略是否生效。
另一种是给所有检索分支传入同一份过滤条件。Elasticsearch RRF retriever 的 filter 会按各子检索器的规范作用于它们,减少两套过滤逻辑逐渐分叉的机会。OWASP LLM08:2025 提出的权限感知存储要求,也应覆盖混合搜索的第二个索引。
过滤正确,还不等于结果完整。pgvector README 的例子是:近似索引先扫描,再应用过滤;条件命中 10% 的行时,HNSW 默认 hnsw.ef_search 为 40,平均只有 4 行留下。过滤列索引可能让规划器选择精确搜索,这个问题针对的是实际走 HNSW 的情况。
作者据此推测:权限越窄,向量候选可能越少,经 Reciprocal Rank Fusion 或 cross-encoder 合并后,关键词结果的影响可能更大。这个排序变化没有实测,不能当成普遍结论。
pgvector 从 0.8.0 起支持迭代索引扫描,可以继续扫描直到找到足够结果。多租户共享近似索引还可能相互影响召回率和速度,README 建议需要租户隔离时采用 list partitioning 或独立表。因此,只用管理员账号评估检索质量,会漏掉普通用户的体验。
缓存和关联记录,都是读取入口
缓存若在检索前直接返回答案,检索里的权限过滤根本不会运行。
原文引用的团队案例中,一个提示词曾让任意员工看到 HR 文档;但作者没有证据说明这是缓存造成的。缓存风险是另一项推断:应用内的语义缓存或答案缓存,必须把调用者的授权范围纳入匹配条件,或者在返回前完成授权。
HTTP 缓存也不能与应用缓存混为一谈。RFC 9111 §3.5 对携带 Authorization 的请求规定了共享缓存限制,并列出 public、s-maxage、must-revalidate 等允许共享复用的条件。Redis 语义缓存不会自动继承这些规则。
另一类入口是间接引用:ID、精确名称、关联指针、范围参数,乃至查询语言。
开发者 Heinrich Neb 描述过一个实际问题:相似度检索已经正确执行范围限制,按精确名称查询却能向任何人返回完整内容。OWASP API1:2023 对应的要求是,每个接收对象 ID 并操作对象的 API,都应执行对象级授权。
指针还可能只泄露「存在性」。一条公开旧记录仍显示私有后继记录的名称,即使没返回正文,也暴露了隐藏信息。指向的记录是否可见,需要在读取时判断。PostgreSQL 文档也提醒,引用完整性检查绕过行级安全,可能产生隐蔽的信息通道。
查询语言则可能把读取范围进一步扩大。Cognee 的 PR #5844 于 2026 年 10 月 15 日合入 dev 分支,涉及 CYPHER、NATURAL_LANGUAGE 搜索类型执行原始 Cypher 的问题;修复让 ALLOW_CYPHER_QUERY 默认变为 false。原文没有确认该修改进入了哪个正式版本。
隐藏记录与不存在的记录,如果返回不同描述,也会泄露存在性。是否需要统一响应,取决于记录的存在本身是否属于秘密。
谁能决定这次读取属于哪个用户
请求带来的 user_id、租户标签或模型生成的参数,都不能直接充当授权依据。
Databricks agent memory 的 Beta 文档,更新于 2026 年 10 月 26 日,要求应用从可信调用上下文设置 actor_id;依赖终端用户身份时,缺少身份就拒绝,不能回退到共享 actor_id。
原文列出的修复说明了三种不同错误:
- Supermemory PR #1824 于 10 月 16 日合入工具包
2.x发布分支。getProfile、documentList、memoryForget、documentDelete曾直接使用模型提供的可选containerTag。修复要求标签属于开发者配置的集合,否则在发请求前失败;Tools3.x的 #1773 已移除模型选择范围的能力。 - Mem0 PR #7581 于 10 月 12 日合并,修复 hosted clients 的
delete_all、delete_users空实体 ID 问题。空 ID 会被 API 当作匹配项目全部记忆。这里涉及删除,且只是客户端修复,原文没有核实服务端后续状态。 - Anis Meziani 在 10 月 14 日描述,其 ADK-native session routes 原先只验证 token 有效,没有验证 token 代表谁。修复后,所有读取 session 或 memory 的路由,包括实时 WebSocket,都要比对 token subject 与
user_id。
身份验证回答「你是谁」,对象授权还要回答「你能读哪条记录」。
共享记忆还有更深一层边界。Databricks 明确说明,actor_id 用于区分记忆,却不是访问控制;能访问 store 的主体可以读写所有 actor 的条目,严格隔离需要按边界建立独立 memory store。
一篇 2026 年 10 月 19 日的预印本 arXiv 2610.25707 v1,在 AppWorld 中混放了 763 条历史登录信息,对所需应用有 84.7% 的情况检索到其他用户凭据,但 agent 很少实际使用。这个数字受实验设置限制,且论文未经同行评审。给凭据标注所有者能减少其压力测试中的多数误用,却不能替代读取授权。
怎样证明权限检查真的生效
原文提出三组探针,每组都需要有权限用户作为正向对照。否则「什么都没搜到」也可能只是检索坏了。
第一组,在受限文档中选一个稀有词,分别跑 vector-only、lexical-only、hybrid。无权限用户不能取得片段,有权限用户必须能够取得。
第二组,让用户 A 先用受限文档生成答案、预热缓存,再让无权限的 B 提同一个问题及其改写。既要确认 A 确实命中了文档,也要比较 B 在缓存开启和关闭时的授权表现。
第三组,尝试按 ID、精确名称读取,沿公开指针访问隐藏记录,再传入别人的 scope 和空 scope。对隐藏记录的响应,应与不存在记录的响应比较;同时证明有权限身份能够走通该路径。
读取入口要从代码或工具注册表里找。原文引用的一个 MCP 案例中,list_tools 按 token scope 过滤后,旧测试通过了,但持有 tickets:read 的 token 仍能执行删除。工具没有显示出来,不代表底层调用被拒绝。
权限变更和删除后要重跑这些检查,索引与缓存里的权限副本可能过期。检查对象也不能止于最终回答:未经授权的内容一旦进入模型上下文,读取边界就已被跨过。
我的判断
我认同它把审计单位从「搜索接口」扩大到「所有返回存储内容的函数」。这比单纯加强提示词更可靠:模型承诺不引用,无法补上服务端遗漏的授权检查。
但数据库策略、统一过滤参数、独立 store 各有适用前提。数据库策略依赖连接角色正确;统一过滤依赖每个入口都确实使用它;独立 store 能建立更明确的边界,也增加管理成本。原文没有提供这些方案的性能和成本对照,不能据此选出通用最优解。
作者所属公司 Mnemoverse 也被列为文档案例:API key 绑定 organization_id,共享 rooms(beta)每次请求检查成员身份,非成员获得 403,且先检查成员身份再判断归档状态。这是厂商声明的边界,作者没有测试产品,也没有确认引用的各项 PR 是否已经发布。它更适合当审计框架,不能当产品安全排名。
能拿来做什么
- 给 Node 服务列出所有返回存储内容的入口,包括详情、列表、导出、调试接口和 AI 工具调用,逐项追踪授权范围从哪里产生。
- 给混合检索建立一份受限文档样本,用两个权限不同的账号分别测试关键词、向量和混合搜索,并按权限层级观察召回质量。
- 给答案缓存增加跨账号复用、权限撤销和删除后的回归检查,同时查看实际送入模型的上下文。
- 用 AI 编程助手检查
user_id、actor_id、containerTag的数据流,重点查找模型参数直接决定范围、空值扩大范围、只验证 token 有效的代码,再人工确认调用链。