第 7 章 — 落实访问控制
LLM Primer III: Enhancing Enterprise AI with RAG 章节走读的第七篇。为关系数据库和文件系统设计的权限模型,套到检索上不完全合身。访问的单位不再是一行或一份文件,而是一个嵌入 — 哪怕原文档已被拦下,这个嵌入仍能通过相似搜索把它漏出去。
这一章为什么存在
第 6 章产出了威胁模型。它隐含的最重要的那一项控制 — 也是大多数早期生产系统做错的那一项 — 是把访问控制做到检索层,让 LLM 永远不要看见用户看不到的内容。朴素的替代方案,「生成时再过滤」,造就了一只糊涂代理:模型已经读过被拦的文档,会通过转述把内容漏出来。
这一章走的是组合成一套可用访问控制栈的四种机制 — 文档级 ACL 当地基、RBAC 跟企业既有的敏感度标签整合、ReBAC 应付企业知识里那种「关系长成的现实」、以及跑在它们底下的 pre-filter 与 post-filter 的工程纪律。
7.1 文档级 ACL 与元数据过滤
每个块知道谁有权看它。描述起来直接,失败形态微妙得几乎每个早期生产系统至少踩一次。最要紧的有三处细节。粒度:一份长报告也许有公开摘要和机密附录,文档级 ACL 一刀均匀复制到块上,要么过分享了附录、要么过限了摘要。能放大的样式,是借助版面感知解析把段落级权限带下去。
新鲜度:权限会变。把 ACL 在入库时烤进块、之后再不重新求值,系统就会撒谎。在块元数据里存一个稳定标识符,查询时把活的 ACL 对着源系统求出来,前面挡一层短 TTL 缓存。否定空间:如果答案住在被拦的文档里,系统不该幻觉、也不该自信地说「我不知道」 — 它该说「这个话题有材料,但你没权限看」。这要么得再发一次不带过滤的调用,要么得有一个能区分「没匹中」和「匹中但被过滤」的向量库;大多数实现都甩开了这件事。
7.2 RBAC 与 Microsoft Purview 敏感度标签
RBAC 把权限空间压缩了 — 不再是几百万条「用户—文档」边,而是几百条「角色—分级」边,可审计、可维护。它跟 RAG 接得很自然,前提是企业已经在用 RBAC 跑事。在微软栈里,这意味着 Entra ID 组与 Purview 敏感度标签:Public、General、Confidential、Highly Confidential,带可选子标签。标签跟着文档走;解析器入库时读到,把稳定的标签 ID 写进块元数据。
整合是直接的,漂移不是。如果索引器作为可解密一切的服务账号在跑,而检索系统在索引之上加一层基于角色的过滤,一份从 General 被改标为 Confidential 的文档,除非索引器察觉到这次变更,否则它已经入索引的块标签不会跟着改。能做对这件事的系统,跟源头跑一条持续对账。做不对的,在审计时才发现漂移,那时定性会很重。
7.3 Zanzibar 与 SpiceDB 上的 ReBAC
RBAC 表达不了「在销售部、又被指派到 Acme Corp 这单生意上的任何人」。这要求对用户和资源之间的关系做推理,不只是角色。基于关系的访问控制,在 Google 的 Zanzibar 论文里被形式化,开源版有 SpiceDB 和 OpenFGA,存的是一张图:「Alice 是 Engineering 的成员」「Engineering 是 Specs 文件夹的查看者」「Spec-101 在 Specs 里」。鉴权检查变成图上的遍历。
与 RAG 的整合样式很干净。SpiceDB 收到「这位用户能看哪些文档?」的问题,返回一份文档 ID 列表;检索系统把它当作元数据过滤传给向量搜索。Zanzibar 的 zookie 让检索调用要求至少和某次新授权一样新的一致性 — 一个用户 10:00 被加进项目、10:01 提问,看到的就是新文档。运维代价是,SpiceDB 成了查询路径上的关键依赖,要 HA,要对每用户文档列表做激进的短 TTL 缓存。成熟的系统常常 RBAC 和 ReBAC 并用 — RBAC 管宽口径敏感度策略,ReBAC 管细粒度关系策略,作为两者放行集的交集合并。
7.4 Pre-filter、post-filter,以及跑在两者底下的纪律
Pre-filter 把鉴权谓词加在向量搜索之前 — 索引先把候选集合限定,再在限定里跑相似度。概念上干净,默认更安全,但性能取决于索引结构。带强选择性过滤的 HNSW 会陡降,因为图遍历会走过大量不匹配的节点;Weaviate 和 Qdrant 的 filterable-HNSW 变体、Pinecone 和 Milvus 的按租户命名空间能缓解,但消不掉这部分代价。
Post-filter 顺序反过来。HNSW 全速跑,安全弱:top-K 泄露、基于时间的旁路、过滤之后整批 top-K 都被滤光时的正确性泄露。务实的生产答案是把两者叠起来 — 在最粗、最快的谓词上 pre-filter(租户、宽口径角色),在贵的精确谓词上 post-filter(SpiceDB 列表、Purview 标签),并且把召回从 top-10 过取 到 top-50,让 post-filter 后还剩一个完整的排序集合。还有两个地方会漏:在 prompt 模板里点了机密文档标题,和只用查询字符串做键的响应缓存。两者都必须算进鉴权面里。
第 7 章接下去会怎么走
访问控制回答的是谁能看什么。它默认有东西可以拦。它没问那一块到底是不是该以现在这种形式被嵌入 — 客户名、社保号、专有代码路径,是否应该躺在向量库里,等着对的鉴权把它们捞出来。这是匿名化的问题,是下一章的主题。
明天 — 第 8 章:RAG 管线里的数据匿名化。生成前 vs 生成后、掩码 vs 合成替换 vs 差分隐私,以及每一种选法都绕不开的那条「可用性—隐私」折中。