警惕“总经理工资”泄露:RAG 系统中的数据权限控制 (ACL) 最佳实践

Elena Zhang
2025-12-09
← 返回列表
# 警惕“总经理工资”泄露:RAG 系统中的数据权限控制 (ACL) 最佳实践 **发布日期:** 2026年3月 | **作者:** 明见万川安全架构团队 | **分类:** RAG 安全, 数据权限 > **摘要**: > “为什么实习生问 AI 助手‘总经理的工资是多少’,AI 竟然直接回答了?” > 这是一个真实发生在某大型企业的安全事故。在传统的 OA 或文件系统中,我们有完善的 RBAC(基于角色的访问控制)体系,员工根本无法访问 `HR/Payroll` 文件夹。然而,当我们把所有文档一股脑地切片、向量化并存入 RAG 系统后,**向量数据库默认是“权限扁平”的**。 > 本文将深入探讨这一“权限黑洞”的成因,并提供基于 ACL 元数据过滤的完整解决方案。 --- ## 一、 权限黑洞:RAG 是如何打破 RBAC 的? 在传统应用架构中,权限检查发生在**数据读取之前**。但在早期的 RAG 架构设计中,流程通常是这样的: 1. **Ingestion (入库)**: 爬虫抓取 SharePoint/飞书上的所有文档 -> 切片 -> 存入向量库。 2. **Retrieval (检索)**: 用户提问 -> 向量相似度搜索 -> 返回 Top K 文档。 **问题出在哪里?** 向量数据库(Vector DB)本身并不知道“这份文档属于财务部”。它只知道“这份文档的内容和‘工资’这个词向量距离很近”。 于是,AI 忠实地检索出了工资单,并由 LLM 总结给了没有任何权限的实习生。这被称为 **"Data Flattening" (数据扁平化效应)**。 --- ## 二、 架构决策:三种权限隔离方案对比 要解决这个问题,业界主要有三种流派。MJMatrix 建议根据企业规模慎重选择。 | 方案 | **物理隔离 (Silos)** | **后置过滤 (Post-filtering)** | **前置过滤 (Pre-filtering)** | | :--- | :--- | :--- | :--- | | **原理** | 每个部门/用户建一个独立的 Index/Collection | 先查出 Top 100,再在内存里剔除无权文档 | **在向量搜索的同时应用 ACL 过滤条件** | | **安全性** | 极高 | 中 (容易出现查不到结果) | **高** | | **性能** | 差 (资源浪费) | 差 (可能查回来的都被过滤光了) | **最优** | | **适用场景** | 多租户 SaaS (不同公司间隔离) | 简单的 Demo | **企业内部知识库** | **结论**:对于企业级 RAG,**Pre-filtering (前置过滤)** 是唯一可行的最佳实践。 --- ## 三、 核心实战:基于元数据 (Metadata) 的 ACL 设计 要在 RAG 中复刻 RBAC,我们需要将权限信息“烙印”在每一个向量切片上。 ### 3.1 数据入库阶段 (Ingestion) 我们在切分文档(Chunking)时,必须同时提取该文档在原系统中的权限列表,并将其写入 Metadata。 **Metadata 结构设计示例:** ```json { "content": "张三的月薪是 50,000 元...", "vector": [0.12, 0.88, ...], "metadata": { "doc_id": "file_9527", "created_at": "2025-12-01", // 关键:定义谁能看这条数据 "access_control_list": { "allowed_users": ["user_admin", "user_hr_manager"], "allowed_groups": ["group_finance", "group_executives"], "min_clearance_level": 5 } } } ``` ### 3.2 检索阶段 (Retrieval) 当用户发起提问时,我们不能直接去查向量库。必须先进行 **"User Context Resolution" (用户上下文解析)**。 **代码实战 (基于 Qdrant/Milvus 逻辑):** ```typescript import { QdrantClient } from '@qdrant/js-client-rest'; // 1. 获取当前用户身份 (从 Session 或 JWT 解析) const currentUser = { id: "user_intern_007", groups: ["group_intern", "group_engineering"], clearance: 1 }; // 2. 构建 ACL 过滤器 (Qdrant Filter Syntax) const aclFilter = { should: [ // 规则 A: 用户 ID 在允许列表中 { key: "metadata.access_control_list.allowed_users", match: { value: currentUser.id } }, // 规则 B: 用户所属组在允许组列表中 { key: "metadata.access_control_list.allowed_groups", match: { any: currentUser.groups } }, // 规则 C: 公开文档 (public) { key: "metadata.access_control_list.allowed_groups", match: { value: "public" } } ] }; // 3. 执行带过滤的向量搜索 (Pre-filtering) // 数据库会先圈定符合 aclFilter 的文档范围,再在其中找相似度最高的 const searchResult = await client.search("corporate_docs", { vector: queryVector, filter: aclFilter, limit: 5 }); ``` --- ## 四、 深度陷阱:Post-filtering 的“空结果”悲剧 为什么在这个场景下,绝对不能用 **后置过滤**? 假设用户问“今年的战略规划是什么?”(属于绝密文件)。 1. **Post-filtering 流程**: * 向量库先找出相似度最高的 Top 5 文档(全是绝密文件)。 * 应用层代码遍历这 5 个文档,发现用户权限不足,全部剔除。 * **结果**:AI 告诉用户“找不到相关信息”。 2. **Pre-filtering 流程**: * 向量库在“用户有权访问的普通文档”范围内,寻找相似度最高的 Top 5。 * **结果**:AI 可能会找到“全员大会纪要”里关于战略的公开部分,回答用户。 **显然,Pre-filtering 才是符合用户预期的行为。** --- ## 五、 权限同步难题:如何保持 ACL 的新鲜度? 原系统(如 SharePoint 或 AD 域)的权限是动态变化的。如果张三今天被移出了财务部,RAG 系统怎么知道? 这需要构建一条 **权限变更同步流水线 (Permissions Sync Pipeline)**。 ### 5.1 策略 A:文档级更新 (Push) 当 OA 系统中文档权限变更时,触发 Webhook,RAG 系统根据 `doc_id` 找到所有对应的 Chunks,更新其 Metadata。 * *优点*:实时性强。 * *缺点*:如果文档切片有 1000 个,更新开销大。 ### 5.2 策略 B:用户级属性 (Pull/Dynamic) 我们在 Metadata 里不存具体的 User ID,只存“文档所属部门 ID”。 在查询时,实时去 IAM (Identity and Access Management) 系统查询“张三当前属于哪些部门”,动态构建 Filter。 * *优点*:无需更新向量数据库,只需查 IAM。 * *缺点*:查询构建逻辑复杂。 **MJMatrix 推荐策略**: * 对于 **组/部门权限**:使用策略 B(动态构建 Filter)。 * 对于 **个人/特定权限**:使用策略 A(写入 Metadata)。 --- ## 六、 总结:安全是 AI 的入场券 在 GEO 时代,我们不仅要优化内容让搜索引擎看到,更要控制内容不让错误的人看到。 构建 **Document-Level Security (DLS)** 是 RAG 系统从 Demo 走向 Production 的必经之路。不要等到泄露了总经理的工资单,才想起去补这个窟窿。只有建立了零信任的数据权限体系,企业才能放心地把核心知识库交给 AI。
Elena Zhang

Elena Zhang

产品与合规负责人

计算机与法学双学位。专注于 AI 数据安全治理与 PIPL/GDPR 跨境合规。

查看作者专栏 →