权限感知检索:别让 RAG 绕过你的权限系统

企业 RAG 有一个和演示环境截然不同的隐形前提:不是所有用户都该看到所有文档。财务制度分密级,合同按主体隔离,HR 文档只对 HR 开放。危险的是,权限系统通常建在应用层——用户 A 打开文档 X 时,应用校验权限;但 RAG 的检索不经过这条路径:向量库查询直接按语义拿回最相关的片段,压根不知道「谁在问」。于是出现了企业 RAG 最经典的事故:员工在知识库里搜到了自己无权查看的高管薪酬文档——因为检索层从未接过权限这一棒。这篇讲权限感知检索(access-aware retrieval)的方案谱系与工程纪律。

问题的确切形状:权限必须进检索层

先把责任划清。应用层的权限校验(接口鉴权、文档访问控制)必须保留——它是最后防线;但它拦不住检索泄露:检索返回的片段直接进入生成,答案把无权文档的内容复述出来,应用层全程没有「看到文档」这个动作可校验。所以权限过滤的唯一正确位置是检索层内部——每次向量查询都携带查询者的身份与权限集,索引在返回结果前完成过滤。

权限模型本身建议直接复用企业现有的 ACL(Access Control List)体系:文档挂权限主体(用户/组/角色),用户查询时带自己的主体集合。不要为 RAG 另造一套权限语义——两套权限体系必然漂移,漂移的那套迟早出事。

方案谱系:三条路及其代价

路线一:post-filter(检索后过滤)。 检索照常返回 top-k,过滤掉无权片段后再送生成。实现最简单——一行过滤代码;但有两个内伤。配额陷阱:top-k 名额被无权文档占掉(查询了 10 条,7 条被滤掉,只剩 3 条可用证据),检索质量随权限收紧而下滑;极端情况下 top-k 全部无权,检索层「看似无结果」而库里其实有答案。延迟信号泄露:命中数量的变化本身在告诉用户「存在一份你无权的相关文档」——多数场景可接受,高敏场景是问题。post-filter 只适合权限覆盖率高的场景(绝大多数文档对绝大多数用户开放,过滤损失小)。

路线二:引擎级 pre-filter(检索时过滤)。 把权限条件作为过滤参数传给向量库,在检索过程中只考虑有权文档。向量数据库普遍支持带 filter 的检索(《元数据过滤》讨论过它对 HNSW 召回的影响与 ef 放大补偿——权限过滤完全同理:权限越严,搜索预算越要放大)。它解决了 post-filter 的配额陷阱,是中小规模多租户的默认推荐。代价与《元数据过滤》一致:过滤条件严时图索引召回下降,需要参数补偿与测试验证。

路线三:按租户/权限域分区。 文档按权限域物理分片(每租户独立 collection、或按权限组分 partition),查询路由到对应分片。它把权限过滤变成「选址」,彻底绕开过滤对图索引的影响,性能与隔离性最好;代价是权限域爆炸时的运维负担(跨域用户、权限矩阵复杂的企业,分片数失控),以及跨域查询(用户有权访问多个域)要合并多路结果重排序。SaaS 多租户(租户数=客户数,域内隔离要求硬)最适合这条路线。

三条路的选择可以压成一句话:权限分布宽松用 post-filter 起步,常规企业场景用引擎级 pre-filter,硬隔离的 SaaS 多租户用分区。

ACL 同步:权限变了,索引要跟上

方案之外,时效是另一场硬仗:权限在源系统里变更(文档转密、员工调岗、组权限调整),索引里的权限元数据必须同步。不同步的后果与文档内容不同步一样严重——「文档内容没变但权限变了」的窗口期里,检索按旧权限放行。同步机制的三种强度:事件驱动(源权限系统发变更事件,索引实时更新元数据——延迟秒级,工程要求高);定时对账(每小时/每天批量比对 ACL 差异并回填——《增量更新与模型迁移》的巡检机制套用);查询时校验(缓存文档 id → 权限主体的映射在查询时实时判定——索引不存权限、每次查询按当前 ACL 过滤,时效完美但查询成本上升)。务实组合通常是:权限元数据随文档摄入写入 + 定时对账兜底 + 高敏文档走查询时校验。

文档级与片段级:权限的粒度选择

权限挂载的粒度是一个容易被跳过的设计决策。文档级 ACL(整个文档一个权限集,块继承文档权限)实现简单、与源系统的权限模型天然对齐,是默认选择;但文档级粒度在「一册多密级」的语料上会失真——一本混合手册里三章公开、一章涉密,要么整册锁死(过度限制)、要么整册放开(事故)。片段级 ACL(块可覆盖文档默认权限)解决混合密级问题,代价是权限元数据的管理复杂度上升:块的权限要随切块动态继承(切块边界变化时权限继承要重算),ACL 同步(《上一节》)也要落到块级。务实的选择:文档级打底,仅对确认存在混合密级的语料启用片段级,并在片段级启用时收紧审计(粒度越细,出错面越大)。另一个相关决策:权限过滤发生在向量检索层还是重排层——重排层过滤(检索多取一些、重排时按权限筛)实现更简单,但配额陷阱更重(《路线一》的放大版),只适合权限覆盖率极高的场景;向量检索层过滤是主路径,别因为「简单」把权限下沉到底层之后又偷懒上浮。

权限开销的量化与预算

引擎级权限过滤的延迟代价要实测并留预算:过滤条件下图索引的召回下降与 ef 放大(《元数据过滤》的机制)直接换来延迟,权限矩阵复杂的企业(一个用户动辄几百个权限组)里,过滤表达式本身的解析开销也进入账单。量化方法:评测集按「权限宽松/中等/严格」分三组,分别测带权限过滤的 P50/P99 与裸检索的对比——经验上严格组的延迟是裸检索的 2~5 倍,ef 放大系数与延迟预算要按组设定,不能一套参数通吃。预算装不下时的降级路径:权限域预聚合——把权限组按「共同可见集」聚类,用户落到预聚合域(几百个权限组收敛为个位数域),过滤表达式从几百条件收敛为单条件 in 一个域标——预聚合表的维护用定时对账(《ACL 同步》),换来的是过滤开销的数量级下降。

权限过滤的测试集

权限场景要有自己的评测组,三种用例:越权探测题(低权限用户问高敏内容——期望拒答或无泄漏,是红队测试的脚本化形态,进回归每次跑);权限内应答题(高权限用户问同主题——期望正常命中,防「过滤过严把有权内容也挡了」的过度矫正,这类 bug 比越权更常见);权限边界题(刚好有权/刚好无权的边界主体、权限变更窗口期的行为)。三种用例的期望输出都要精确断言(命中清单/拒答),不是打分制——权限是布尔问题,0.99 的正确率意味着 1% 的泄露或误挡,两者都不可接受。把这组用例挂进 CI,权限相关代码(过滤表达式、ACL 同步、预聚合表)的每次改动都全量跑——这一组的护栏价值,前面所有效果类评测加起来也比不上。

默认拒绝:架构原则大于实现细节

最后讲一条凌驾于所有实现之上的原则:默认拒绝(deny by default)。权限过滤的任何一处代码,失败的形态必须是「返回更少的结果」而不是「返回更多」:查询者的权限信息缺失时——不检索(而不是当公开用户);权限解析报错时——返回错误(而不是跳过过滤);新文档权限未知时——不可见(而不是当公开文档)。「fail open」在权限系统里是事故的同义词。配套的纪律还有两条:审计日志(谁、何时、检索命中了哪些文档——泄露事故的追溯全靠它);红队测试(用低权限账号跑一组「越权探测查询」,验证检索层确实拿不到高敏内容——把它放进上线 checklist 与回归测试)。

租户标识的传递链

多租户落地还有一个高频坑值得单列:租户标识的传递链。查询请求从网关到应用再到检索层,租户与用户标识要全程透传——任何一层「顺手丢了上下文」(异步任务里丢了用户身份、批处理里用了固定服务账号),权限过滤就退化为「按服务账号的权限」,而服务账号往往是超管,一次丢失等于权限全开。防线是接口设计强制要求显式传入权限集参数(没有默认值,缺参即拒绝),让「忘记传」在参数校验期就失败,而不是在运行期静默放行。权限事故里「标识在某层被默认值顶替」的占比高得惊人——把默认值从接口里删掉,是最便宜的一道闸。

小结

权限感知检索的本质是把权限从应用层的一道关卡,下沉为检索层的一个维度。方案三选一(宽松 post-filter、常规 pre-filter、硬隔离分区),ACL 同步选强度(事件、对账、查询时校验按需组合),架构上守住默认拒绝与审计、红队两条纪律。在所有 RAG 工程项目里,这一篇的主题有个特殊地位:其他环节做砸了损失的是效果,这一环节做砸了损失的是公司——它值得在你上线的 checklist 里排第一位,而不是「下个迭代再说」。

参考资料

← 返回资讯列表

读者留言

COMMENTS 暂无
仅本站原创文章开放留言 · 请勿留下手机号、邮箱等个人信息

还没有留言,来说第一句?