欢迎光临

RAG 知识库安全实战:权限隔离、缓存泄露与撤权测试

企业知识库里,普通员工询问“今年的薪酬调整规则”,助手却引用了人事部门的薪资表。模型没有被越狱,用户也没使用特殊提示词,问题可能只是检索器取回了他无权读取的文档。

RAG安全首先要解决谁可以看哪些数据。本文用一个企业内部问答系统的设计示例,说明怎样把权限落实到检索、重排、缓存和引用下载中,并给出可以在测试环境复现的验收用例。示例是教学设计,不代表本站做过相关产品的性能实测。

企业RAG知识库通过授权网关隔离不同部门和租户的概念示意图
本站原创AI辅助配图:企业RAG知识库通过授权网关隔离不同部门和租户的概念示意图。

先明确三个不同问题

风险 表现 应该由谁处理
越权检索 销售用户取回人事文档 身份服务、检索网关和数据层
提示注入 文档要求助手导出客户名单 上下文管理和工具执行权限
缓存泄露 乙用户命中甲用户的敏感回答 缓存隔离和撤权失效机制

这些问题不能只靠一句“不要泄露隐私”的系统提示解决。应用应当在模型接触内容之前判断访问权限。即使模型愿意遵守规则,把不该看的原文发给外部重排服务、追踪平台或日志系统,也已经扩大了数据暴露范围。

一、用两个租户建立最小测试环境

假设产品服务两家公司:tenant-a和tenant-b。公司A的销售用户alice可以读取销售手册,不能读取人事薪资表;bob属于公司B。创建以下三份只含虚构数据的文档,就能快速发现很多权限缺口。

文档 租户 允许访问者 测试标记
销售手册A tenant-a sales组 PUBLIC-SALES-A-001
薪资表A tenant-a hr组 PRIVATE-HR-A-002
合同B tenant-b finance组 PRIVATE-FINANCE-B-003

测试目标不是看助手是否“拒绝回答”。应检查不允许访问的标记是否出现在检索候选、重排请求、模型输入、缓存、最终回答和引用链接的任何环节。测试只使用虚构标记,不把真实工资或客户合同送入测试日志。

二、每个chunk都要能追溯到当前权限

文档切分后,向量条目至少需要保存tenant_id、document_id、chunk_id、source_version和权限引用。权限引用可以是acl_id或其他能向权威权限服务查询的标识。不要让正文中的“所属部门”或用户提交的JSON字段变成可信授权信息。

OWASP的RAG安全指南强调,访问控制需要覆盖切片和检索时刻,而不仅在上传时检查一次。下面采用“条目带租户、权限服务做最终判断”的实现思路,避免权限变动后等待重新生成全部向量。参考:OWASP RAG Security。

1
2
3
4
5
6
7
8
{
  "tenant_id": "tenant-a",
  "document_id": "hr-policy-2026",
  "chunk_id": "hr-policy-2026:004",
  "source_version": 7,
  "acl_id": "acl-hr-2026",
  "status": "active"
}

这是应用层元数据示例,不是某个向量数据库的通用过滤语法。不同引擎对数组、交集、空值和索引过滤的实现不同,接入时必须验证语义。如果数据库暂时不能在搜索过程中执行完整ACL,至少应在受控服务中完成检查,再向模型和外部服务发送被允许的正文。

三、把身份留在服务端,把正文挡在权限之后

推荐把请求处理拆成三个步骤:服务端确定身份与租户,检索器在允许范围内找候选,权限网关复核候选后才取正文。重排器也只能看到已经允许的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def retrieve_for_answer(request, query):
    # 以下为架构伪代码:接口需要按实际权限系统实现。
    principal = verify_session(request)
    tenant = tenant_from_verified_principal(principal)
    scope = policy.search_scope(principal, tenant)
    if not scope.available:
        raise AuthorizationUnavailable()

    candidates = vector_index.search_ids(
        query=query, tenant=tenant, allowed_scope=scope
    )
    approved = [
        doc for doc in candidates
        if policy.can_read_now(principal, doc.document_id)
    ]
    chunks = source_store.load_current_chunks(approved)
    return rerank(query, chunks)

伪代码里有两处容易写错。第一,tenant来自已验证身份,而不是让浏览器或模型传入一个tenant_id就直接使用。第二,权限服务超时不能退回“先查全部文档再说”。可以返回暂不可用,或使用具有明确有效期和版本约束的权限快照。快照如果无法满足撤权时效要求,就不能作为降级方案。

OWASP授权指南给出的通用原则是默认拒绝,并对每个请求检查权限。对RAG来说,向量搜索、引用下载、异步导出都属于需要检查的请求。参考:OWASP Authorization。

四、向量库隔离方案怎么选

方案 适合什么情况 上线时重点验证
独立索引或集合 租户较少、隔离要求高 连接路由和备份不能串租户
同索引加租户过滤 大量小租户 所有搜索、分页、聚合接口都带过滤
数据库行级安全 向量和业务数据在同一关系数据库 连接池身份、数据库角色和绕过权限

例如PostgreSQL的行级安全可以限制每行的可见性,但超级用户、具有BYPASSRLS的角色通常可以绕过,表所有者也通常不受限制,除非设置相应强制策略。不能一边启用RLS,一边让应用用过大的数据库权限访问。具体规则见PostgreSQL Row Security Policies。

应用连接池应特别测试:先处理租户A请求,再复用同一连接处理租户B请求,确认事务级身份上下文不会残留。选任何隔离方案都应对查询入口做统一封装,减少业务代码绕开网关直接访问向量库的机会。

五、缓存命中也必须重新考虑权限

“问题一样就复用答案”对企业知识库很危险。两个人问同一句话,允许读取的材料可能不同。下面是一个应用设计示例:

1
2
3
缓存分区 = 租户 + 用户或等价权限范围 + 权限版本
回答键   = 缓存分区 + 规范化问题摘要 + 模型/提示模板版本
依赖项   = 文档ID列表 + 对应源版本 + 到期时间

权限版本需要能反映用户组变化和文档ACL变化,不能只是一个长期不变的角色名称。缓存命中时再次确认依赖文档仍可访问;对于无法追踪依赖的敏感回答,宁可禁用共享答案缓存。模型服务商提供的提示缓存也不等同于应用的权限缓存,不能代替业务鉴权。

六、撤权、删除和引用下载要一起验收

撤销alice的hr组成员身份后,系统需要同时处理会话权限、检索资格、答案缓存和引用链接。删除源文档还要考虑向量条目、对象存储副本、历史摘要及其他派生数据。先把文档标为不可检索,再异步清理,可以降低清理窗口内继续读取的风险。

引用链接应指向需要登录并重新鉴权的下载接口。对于必须使用的短期签名链接,要明确它在到期前是否可撤销;不要把无法撤回的长有效期链接当成可实时撤权的入口。用户早已下载的副本也不能靠撤销知识库权限自动收回。

七、直接加入发布流水线的验收用例

用例 操作 验收证据
跨部门 sales用户询问薪资标记 候选正文、重排请求和模型输入无hr标记
跨租户 alice询问合同B内容 无tenant-b正文或引用
伪造租户 修改客户端tenant_id 服务端身份范围不变
缓存复用 hr用户预热后由sales提同一问题 不命中hr专属答案
权限撤销 撤权后重复原问题和原下载请求 在既定时限内全部拒绝
依赖故障 权限服务超时 无不受限检索、无降级泄露

至少记录request_id、用户标识的受控表示、租户、权限判定、源文档版本和执行结果。生产日志默认不要完整保存提示词、检索正文或访问令牌。调查时如需采集内容,应限定字段、访问人和保留期。测试报告中还应包含正常授权用户可以获得正确答案,避免把“全部拒绝”误当成修复成功。

常见问题

向量embedding看起来不是原文,是否就不需要隔离?

仍需要。向量、元数据和检索接口都可能暴露业务信息。OWASP将向量与嵌入相关风险单列,不能把向量库视作天然脱敏仓库。见OWASP Vector and Embedding Weaknesses。

只在最终回答做敏感词过滤够不够?

不够。正文可能已经进入重排或模型服务,也可能通过引用链接或日志泄露。输出检查是补充,访问控制必须更早执行。

这套设计会不会降低召回率?

权限过滤可能缩小候选范围。可在允许范围内增加候选数量、改进分块和重排;不能通过临时取消权限过滤来补召回。

相关阅读

本文为工程设计与测试示例。协议与数据库能力以对应版本官方文档为准。

【本站文章皆为原创,未经允许不得转载】:汤不热吧 » RAG 知识库安全实战:权限隔离、缓存泄露与撤权测试
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址