← 返回列表
# 警惕“总经理工资”泄露: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。
