第六章 数据安全质询
6.3 字段级权限设计
承前:6.2 节解决了"数据出门前洗不洗"的问题,但安全评审还留下一个尾巴:脱敏管的是发给模型的数据,那人呢?门店店长、一线运营、增长副总裁,都通过 Agent 查询会员数据——谁能看到什么,不该一刀切。这一节我们把控制粒度从"数据级"推进到"字段 × 角色"级:谁,在什么场景下,能看到哪个字段。这是 Agent 项目里权限设计的深水区,也是很多团队踩坑的地方——因为大家习惯的"系统级权限"在 Agent 架构下会失效。
6.3.1 为什么 Agent 会打破传统权限模型
传统 BI 系统的权限是"报表级"的:店长看门店报表,总监看全公司报表,权限跟着报表走。Agent 换了游戏规则——所有人问同一个对话窗口,窗口背后是同一个模型、同一套工具。如果工具层不区分调用者身份,会发生什么?
沙盘里真实发生过:门店店长在 Agent 对话框里问"我们店这周流失风险最高的会员是谁,帮我准备挽回话术"。Agent 调用会员查询工具,返回了 Top 10 会员的完整档案——包括手机号和逐笔消费明细。店长本不该看全量消费明细(跨品类跨门店),手机号更不该出现在一线界面上。这次没出事,但它暴露了一个结构性风险:Agent 是权限的放大器——原来分散在十几个受控报表里的字段,现在一个对话就能聚合带出来。
这就是为什么字段级权限(Field-Level Security / Column-Level Security)在 Agent 项目里从"加分项"变成了"必答题"。设计目标一句话:权限跟着字段和角色走,而不是跟着界面走——不管数据从报表出、从 API 出、还是从模型嘴里说出来,同一个角色看到的字段集合应该一致。
6.3.2 先做权限矩阵:字段 × 角色
字段级权限设计的第一步不是写代码,是填表。把 6.1 节的字段清单和干系人地图(第 2 章)做交叉:
字段级权限矩阵(青禾沙盘 · 简化示例)
字段 店长 一线运营 数据分析师 增长副总裁 Agent(模型)
────────────────────────────────────────────────────────────────────
member_id哈希 ✓ ✓ ✓ ✓ ✓
member_level ✓ ✓ ✓ ✓ ✓
门店消费汇总 ✓ ✓(本组) ✓ ✓ ✓
品类偏好 ✓ ✓ ✓ ✓ ✓
逐笔消费明细 ✗ ✗ ✓(脱敏) ✓ ✓(仅汇总口径)
phone ✗ ✗(掩码外呼) ✗ ✗(掩码) ✗(不出链路)
address ✗ ✗ 城市 城市 城市(泛化)
生命周期标签 ✓ ✓ ✓ ✓ ✓
─────────────────────────────────────────────────────────────────────细看这张表有三个值得展开的设计点:
手机号是"调用权"不是"查看权"。 一线运营要给流失会员打电话,需要手机号——但她不需要"看到"手机号,只需要"系统替我拨出去"。所以权限上设计成:外呼工具可以拿手机号做参数,但界面展示层强制掩码,对话历史里也只存掩码值。"用"和"看"分离,是字段级权限里最实用的一条技巧。
副总裁不是权限最大的人。 直觉上"职级越高看越多",但安全设计的惯例恰恰相反——职级越高,接触明细的场景越少,越应该只看聚合。真正需要明细的是分析师和执行层,而他们恰恰要用脱敏明细。权限跟着"业务需要"走(need-to-know 原则),不跟着职级走,这一点要在评审时跟客户讲清楚,否则会有高管觉得被冒犯。
Agent/模型自己也要占一列。 模型不是天然的"超级用户"——它能看到什么字段,应该由它执行的任务决定(6.2 节的脱敏策略已定),权限矩阵里给它单独一列,避免"人能看的模型都能看"的默认泄漏。
6.3.3 三层实现:应用层、数据库层、API 层
矩阵定了,在哪一层强制执行?三个选择,不是三选一,而是纵深防御,多层同时做:
数据库层(最底层,最刚性):数据库视图或行/列级安全策略(如主流数仓的行级安全 RLS、列级掩码策略)。分析师连的是"消费明细脱敏视图",物理上就没有明文字段可查。优点:绕不过去——不管上层应用写成什么样,库层红线兜底。代价:每加一个角色要改库端策略,灵活性低,DBA 资源要排队。
API 层(中间层,最常用):Agent 底座的工具接口按调用者身份过滤响应字段。Agent 调用 query_member_profile(member_id) 时,工具内部先查权限矩阵,再决定返回哪些字段、哪些字段做掩码。这是 Agent 项目的主战场——因为模型接触数据的唯一通道就是工具,把权限做进工具,就掐住了七寸。实现上通常是在工具注册框架里加一个 required_role / field_policy 声明,由网关统一鉴权后注入执行上下文。
应用层(最外层,兜底展示):前端和对话渲染层做最后一道过滤——即便上游失误漏了,展示层把手机号强制掩码。它的局限是可被绕过(懂技术的人直接调 API),所以只能作为兜底,不能作为唯一防线。
一个伪代码示意(API 层核心逻辑):
FIELD_POLICY = {
"member_profile": {
"phone": {"roles": ["callcenter_tool"], "transform": "mask"},
"consume_detail": {"roles": ["analyst", "vp"], "transform": "by_role"},
"address": {"roles": ["*"], "transform": "generalize_city"},
"member_level": {"roles": ["*"], "transform": None},
}
}
def tool_response_filter(user_role, data):
"""在工具结果返回给 Agent 之前,按角色过滤字段"""
out = {}
for field, value in data.items():
policy = FIELD_POLICY["member_profile"].get(field)
if policy is None: # 未登记字段默认拒绝
continue
if user_role not in policy["roles"] and "*" not in policy["roles"]:
continue # 无权限:字段直接消失
out[field] = apply_transform(value, policy["transform"], user_role)
return out三个实现细节值得注意:未登记字段默认拒绝(白名单制,防止新字段悄悄漏过)、权限过滤做在返回给 Agent 之前(不要指望 prompt 里嘱咐模型"别看手机号"——那不是权限,是请求)、transform 可以按角色分级(分析师看哈希化的明细,副总裁看聚合)。
6.3.4 沙盘应用:一次差点发生的越权演示
这套机制是怎么在沙盘里立功的?受控试点第二周,运营经理在演示时随口问了一句:"帮我查一下会员张三的电话,我要核对一下他的积分。"——张三是她自己的家人,她觉得"就查一个,没事"。Agent 调用工具时,权限过滤器发现"一线运营"角色对 phone 字段无查看权限,返回的是掩码值,并附了一句提示:"该字段需要外呼场景,是否发起外呼任务?"
事后复盘,这件事的价值不在拦住了多严重的事,而在拦截被记录了下来:权限过滤日志里有一完整条目——时间、角色、请求字段、拦截原因。月度安全评审时,信息安全部门看到这份日志,第一次对 Agent 的权限体系给出了主动好评。对 To B 项目来说,可审计的拦截记录比拦截本身更有说服力——它证明控制体系在持续工作。
顺带处理一个真实矛盾:店长的"挽回话术"场景确实需要联系会员。最终方案是给店长开放"外呼任务发起权"(系统代拨 + 通话记录留痕),而不是开放"手机号查看权"。两周后店长反馈说反而更好用——不用自己抄号码了。权限收紧不必然降低体验,好的权限设计会逼出更好的产品交互。
6.3.5b 沙盘里权限体系搭建的两周复盘
把 6.3.3 的三层实现真正落到青禾环境里,用了两周。时间线值得记录,因为它是"纸面方案到客户环境"距离的样本:
第一周前半,做数据库层。客户 DBA 只给了两天配合窗口——提前准备好了视图 DDL 脚本和变更说明,两天刚好够建脱敏视图、跑数据一致性校验。教训:跟客户基础架构团队合作,要把他们的时间窗口当作稀缺资源预约,并带着可执行的东西进场——"我们想讨论一下怎么做"和"这是脚本,请审批"是两种完全不同的对接体验。
第一周后半,做 API 层。这是 Agent 底座自己的地盘,进展最快:在工具注册框架里加了 field_policy 声明,网关统一鉴权后注入角色上下文,过滤器按 6.3.3 的伪代码逻辑实现。中途发现一个设计问题:权限判定需要"当前用户角色",但对话式接口的会话恢复场景(用户隔天回来继续聊)角色可能已变——最终把角色注入放在每次请求的鉴权环节而不是会话创建环节,代价是每次请求多一次 IAM 查询,可接受。
第二周,做应用层兜底和日志联调。展示层掩码规则、权限拦截日志接入客户 SIEM(安全信息事件管理系统)、与信息安全部门对日志字段格式——最后这步花了整整两天,因为客户的日志规范里有严格的字段命名要求。联调的时间永远比写代码长,这条经验在权限这种横切多层的功能上尤其成立。
两周里最大的收获不是代码,是一份《权限实施说明》文档:三层各拦什么、日志长什么样、如何验证拦截生效(附三条测试用例和截图)。这份文档后来成了客户信息安全部门对内培训的教材——你的工程产物变成了客户组织的知识,这是 FDE 影响力的最深形态。
6.3.6 常见坑与自检清单
- 只防人、不防模型:传统权限体系管的是界面账号,Agent 时代模型通过工具访问数据,这条通道必须纳入权限矩阵——给 Agent 一列。
- 权限快照化:人员转岗、角色变化后权限矩阵不同步。对策:权限源统一挂到客户的 IAM/HR 系统,定期对账,别在 Agent 底座里手工维护第二份真相。
- 工具粒度太粗:一个"查会员"工具返回全部字段,权限过滤变成事后裁剪,日志里会出现大量"请求了但被裁掉"的噪音。工具设计时就按字段组拆分,权限声明才干净。
- 忘了对话历史:对话日志里存着历史响应,当时的权限不代表现在的权限——回放历史对话时要做二次过滤,或者日志存的时候就存过滤后的版本。
自检三问,交付前过一遍:每个进入链路的字段在权限矩阵里都有归属吗?模型的工具通道和人的界面通道共用同一套权限源吗?每一条权限判定都有日志可查吗?
6.3.7 要点总结
- Agent 是权限放大器:对话式入口会把分散在报表里的字段聚合带出,字段级权限(字段 × 角色)从加分项变成必答题。
- 先填权限矩阵再写代码;遵循 need-to-know 原则——权限跟业务需要走,不跟职级走;模型自己在矩阵里占一列。
- "用"与"看"分离:手机号做成调用权(系统代拨)而非查看权,是保护 PII 同时不伤业务的钥匙。
- 三层实现做纵深防御:数据库层最刚、API/工具层是 Agent 场景主战场(白名单制、过滤在返回模型之前)、应用层只做兜底展示。
- 可审计的拦截记录比拦截本身更有说服力;防住权限快照化与对话历史泄漏两个尾巴。
参考资料
- OWASP Top 10 for LLM Applications 2025 — LLM02 敏感信息披露与 LLM06 过度授权(Excessive Agency),工具权限最小化是官方推荐缓解措施
- OWASP LLM and Gen AI Data Security Best Practices — OWASP 官方数据安全最佳实践指南,纵深防御与最小权限原则的落地参考
- PostgreSQL 行级安全(RLS)文档 — 数据库层行/列级控制的官方实现参照,多种主流数仓均有等价能力
- Microsoft Presidio(GitHub) — 掩码等字段变换操作的工程实现基础