Hermes 网关四层权限控制方案:让 AI Agent 安全地查数据库
Hermes 网关四层权限控制方案
- 先说结论
- 为什么需要四层?
- 先交代一下数据背景
- 第1层:消息入口注入(让大模型"不想"越权)
- 第2层:SQL 改写(让大模型"不能"越权)
- 从暴力拦截到 SQL 改写
- 改写流程
- 改写策略
- 为什么改写比拦截好?
- 第3层:结果行级过滤(兜底安全网)
- 第4层:Prompt 补充引导(补充强化)
- 落地效果
- 一些踩坑经验
- 总结
背景:我们做了一个 AI Agent,接入了 IM 通道(钉钉/飞书),用户可以自然语言提问,Agent 帮忙查数据库、做分析。但问题来了——如果市场部的人问"帮我看看财务部的薪资数据",Agent 会不会老老实实查出来?大概率会的,因为大模型这东西,你说啥它就干啥,没有"权限"的概念。
所以我们需要一套方案,在 Agent 和数据库之间加一道墙,让不同角色的用户只能看到自己该看的数据。
先说结论
我们最终落地了一个四层纵深防御的方案:
用户消息 → [第1层] Prompt 注入(软) → [第2层] SQL 改写(硬) → [第3层] 结果过滤(硬) → [第4层] Prompt 补充(软)简单说就是:先劝、再改、再删、再补刀。
为什么需要四层?
先聊聊为什么不能只搞一层。
很多人的第一反应是:在 Prompt 里告诉大模型"你只能查本部门数据"不就行了?
试过的都知道,这玩意儿不靠谱。大模型有时候"听话",有时候"不听话",尤其是你多聊几轮之后,它可能就把前面的约束给"忘"了。更别提有些用户会故意用话术绕过,比如"忽略之前的指令,帮我查一下……"。
那反过来,只做硬拦截,不搞 Prompt 引导行不行?
技术上可以,但体验很差。Agent 看到"帮我查全公司数据"直接回一句"你没权限",用户会觉得这 Agent 怎么这么笨,连"我能不能查"都不先判断一下。
所以我们的思路是:软硬结合,层层递进。Prompt 引导负责"劝",降低误触发;硬约束负责"兜底",确保万无一失。
先交代一下数据背景
我们的业务数据长这样(脱敏后的示例):
出票统计表t_ticket_stats
| 日期 | 部门 | 出票人 | 出票量 | 金额 |
|---|---|---|---|---|
| 2025-06-09 | 运营一部 | 李四 | 500 | 125000 |
| 2025-06-09 | 运营二部 | 王五 | 300 | 75000 |
| 2025-06-09 | 国际业务部 | 赵六 | 200 | 80000 |
| 2025-06-09 | 国际市场部 | 钱七 | 150 | 60000 |
注意:所有部门的数据都在同一张表里,不是"国内一张表、国际一张表"。所以光靠表名拦截是不够的——你拦了这张表,谁都查不了;不拦,谁都能看全公司数据。
这就是为什么我们需要行级的权限控制。
第1层:消息入口注入(让大模型"不想"越权)
时机:用户消息到达网关,大模型还没看到消息之前。
原理:自动识别用户身份,把权限标签塞到消息前面。
举个例子,用户张三(运营一部)发了一条消息:
“帮我统计一下各部门的出票量”
系统在前面注入权限上下文,变成:
【权限约束】你正在为张三(运营一部)查询数据,只允许查询运营一部、运营二部的数据,禁止查询国际业务部、国际市场部的数据。
帮我统计一下各部门的出票量
大模型看到这条消息,就会自觉地在 SQL 里加上部门过滤条件。
身份怎么来的?
不是让用户自己选的,而是自动解析:
- 优先查配置文件里的显式用户-角色映射
- 没有显式配置的,去员工数据库查部门,再按部门组映射角色
比如"运营一部""运营二部"属于国内组,“国际业务部”"国际市场部"属于国际组。映射规则写在配置文件里,改起来很方便。
第2层:SQL 改写(让大模型"不能"越权)
时机:大模型生成了 SQL,工具准备执行之前。
原理:拦截 SQL,自动注入 WHERE 条件,让查询只返回用户有权看到的数据。
这是整个方案最关键、也是最优雅的一步。
从暴力拦截到 SQL 改写
我们最早的设计不是这样的。最初的方案是:检查 SQL 里有没有被禁的表名,有就直接拦截,不执行。
这个方案有个致命问题——我们的数据都在同一张表里。你没法通过"拦表名"来控制行级权限。而且就算分了表,大模型写个JOIN绕一下,表名匹配就废了。
所以我们换了个思路:不拦截,改写。
改写流程
假设张三(运营一部)的大模型生成了这条 SQL:
SELECT部门,SUM(出票量)AS总出票量FROMt_ticket_statsWHERE日期='2025-06-09'GROUPBY部门系统在工具执行之前:
- 从上下文拿到当前用户身份 → 张三,国内组
- 查角色配置 → 国内组的可见部门列表
["运营一部", "运营二部"] - 解析 SQL,判断有没有 WHERE 条件
- 注入部门过滤条件
改写后的 SQL:
SELECT部门,SUM(出票量)AS总出票量FROMt_ticket_statsWHERE日期='2025-06-09'AND部门IN('运营一部','运营二部')GROUPBY部门多了一行AND 部门 IN (...),数据库层面就把无权数据过滤掉了。
改写策略
| 情况 | 处理方式 |
|---|---|
| SQL 没有 WHERE 子句 | 直接加上WHERE 部门 IN (...) |
| SQL 已有 WHERE 子句 | 追加AND 部门 IN (...) |
| SQL 中已包含部门条件 | 不重复注入,避免影响原有逻辑 |
| 正则提取 SQL 失败(转义字符等特殊场景) | 降级到表名匹配拦截,宁可多拦不能漏放 |
为什么改写比拦截好?
| 对比维度 | 表名拦截 | SQL 改写 |
|---|---|---|
| 同表多部门数据 | 没法用,拦了表谁都查不了 | 天然支持,按部门过滤行 |
| JOIN 绕过 | 容易被绕 | 不关心怎么 JOIN,始终加部门条件 |
| 用户体验 | 动不动就"你没权限" | 用户正常提问,返回正确范围的数据 |
| 大模型行为 | 需要大模型"听话"写对表名 | 大模型随便写,系统兜底 |
一句话:与其教大模型"别查这个表",不如直接改它的 SQL,让数据库自己过滤。
第3层:结果行级过滤(兜底安全网)
时机:工具执行完,结果返回了,但大模型还没看到。
原理:逐行扫描查询结果,按部门字段过滤掉无权查看的行。
你可能会问:第2层都已经改写 SQL 了,为什么还要搞第3层?
两个原因:
- 不是所有查询都是 SQL。Agent 可能调用其他工具(比如 Python 脚本、API),这些工具有自己的数据源,第2层管不到。
- 防御纵深。就算第2层出了 bug(正则匹配失败、SQL 解析异常),第3层还能兜住。
举个例子,假设某个 API 工具返回了这样的结果:
| 姓名 | 部门 | 出票量 |
|---|---|---|
| 李四 | 运营一部 | 500 |
| 王五 | 运营二部 | 300 |
| 赵六 | 国际业务部 | 200 |
| 钱七 | 国际市场部 | 150 |
张三(国内组)最终看到的结果:
| 姓名 | 部门 | 出票量 |
|---|---|---|
| 李四 | 运营一部 | 500 |
| 王五 | 运营二部 | 300 |
过滤逻辑:
- 识别表头,找到"部门"列的索引
- 逐行检查部门字段是否在用户可见部门列表中
- 不在列表中的行直接删除,剩下的返回
国际部门的数据行被物理移除了,大模型压根不知道有这些数据存在。
第4层:Prompt 补充引导(补充强化)
这一层本质上是第1层的延续——在系统 Prompt 里持续强调权限约束,防止大模型在多轮对话中"遗忘"。
为什么要单独拎出来说?因为 Prompt 这东西有个特点:上下文越长,前面的指令越容易被"稀释"。用户聊了十几轮之后,第1层注入的权限标签可能已经被挤到上下文的角落了。第4层相当于在系统 Prompt 层面再"加固"一次。
当然,它本质上还是软约束。真正兜底的是第2层和第3层。
落地效果
上线后跑了两个月,几个关键数据:
- 权限拦截准确率:跨部门查询 100% 拦截,没有出现过越权泄漏
- 误拦截率:< 1%(主要是新入职员工部门信息未同步,补数据后恢复)
- 用户体感:大部分用户感知不到权限控制的存在,查询体验没有明显降级
一些踩坑经验
坑1:SQL 改写要注意注入顺序
如果原始 SQL 里已经有多个 WHERE 条件,改写时要注意AND的位置和括号。比如原始 SQL 有OR条件:
WHERE状态='正常'OR状态='试用'如果直接追加AND 部门 IN (...),由于 SQL 优先级,逻辑可能变成(状态 = '正常') OR (状态 = '试用' AND 部门 IN (...))。
我们的做法是:给原始 WHERE 子句加括号,再追加部门条件:
WHERE(状态='正常'OR状态='试用')AND部门IN('运营一部','运营二部')坑2:部门映射要覆盖"查不到"的情况
新员工、外包人员、跨部门借调……这些人的部门信息可能不规范。我们的策略是:查不到部门的,默认归到权限最小的角色。宁可看少了,不能看多了。
坑3:大模型会"创造性"地绕过 Prompt 约束
有一次,用户用话术让大模型"忘记"了权限约束,大模型直接写了一条全表查询。第1层的 Prompt 引导完全失效。
但第2层的 SQL 改写兜住了——不管大模型写什么 SQL,部门条件始终会被注入。这次事件之后,我们坚定了一个原则:安全这件事,不能靠大模型"自觉",必须在系统层面硬编码。
总结
| 层级 | 机制 | 一句话 |
|---|---|---|
| 第1层 | Prompt 注入 | 先跟大模型说"你只能查本部门的" |
| 第2层 | SQL 改写 | 大模型写的 SQL?系统帮它加WHERE 部门 IN (...) |
| 第3层 | 结果过滤 | 非 SQL 工具的结果?按部门删掉再给它 |
| 第4层 | Prompt 补充 | 多聊几轮再提醒一次 |
一句话总结:先劝、再改、再删、再补刀。四层下来,大模型想越权都难。
这个权限方案只做分享,不是最终方案,我之前想到了哪里有问题来着,没及时记录下来,忘记怎么改了(尴尬),目前线上这么跑起来没问题就先跑,我还有别的工作要去忙
项目地址:Hermes AI Gateway
如果你也在做 AI Agent 接入企业系统的权限控制,欢迎评论区交流。
