Hermes 网关四层权限控制方案:让 AI Agent 安全地查数据库

Hermes 网关四层权限控制方案

背景:我们做了一个 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:

sql 复制代码
SELECT 部门, SUM(出票量) AS 总出票量
FROM t_ticket_stats
WHERE 日期 = '2025-06-09'
GROUP BY 部门

系统在工具执行之前:

  1. 从上下文拿到当前用户身份 → 张三,国内组
  2. 查角色配置 → 国内组的可见部门列表 ["运营一部", "运营二部"]
  3. 解析 SQL,判断有没有 WHERE 条件
  4. 注入部门过滤条件

改写后的 SQL:

sql 复制代码
SELECT 部门, SUM(出票量) AS 总出票量
FROM t_ticket_stats
WHERE 日期 = '2025-06-09'
  AND 部门 IN ('运营一部', '运营二部')
GROUP BY 部门

多了一行 AND 部门 IN (...) ,数据库层面就把无权数据过滤掉了。

改写策略

情况 处理方式
SQL 没有 WHERE 子句 直接加上 WHERE 部门 IN (...)
SQL 已有 WHERE 子句 追加 AND 部门 IN (...)
SQL 中已包含部门条件 不重复注入,避免影响原有逻辑
正则提取 SQL 失败(转义字符等特殊场景) 降级到表名匹配拦截,宁可多拦不能漏放

为什么改写比拦截好?

对比维度 表名拦截 SQL 改写
同表多部门数据 没法用,拦了表谁都查不了 天然支持,按部门过滤行
JOIN 绕过 容易被绕 不关心怎么 JOIN,始终加部门条件
用户体验 动不动就"你没权限" 用户正常提问,返回正确范围的数据
大模型行为 需要大模型"听话"写对表名 大模型随便写,系统兜底

一句话:与其教大模型"别查这个表",不如直接改它的 SQL,让数据库自己过滤。


第3层:结果行级过滤(兜底安全网)

时机:工具执行完,结果返回了,但大模型还没看到。

原理:逐行扫描查询结果,按部门字段过滤掉无权查看的行。

你可能会问:第2层都已经改写 SQL 了,为什么还要搞第3层?

两个原因:

  1. 不是所有查询都是 SQL。Agent 可能调用其他工具(比如 Python 脚本、API),这些工具有自己的数据源,第2层管不到。
  2. 防御纵深。就算第2层出了 bug(正则匹配失败、SQL 解析异常),第3层还能兜住。

举个例子,假设某个 API 工具返回了这样的结果:

姓名 部门 出票量
李四 运营一部 500
王五 运营二部 300
赵六 国际业务部 200
钱七 国际市场部 150

张三(国内组)最终看到的结果:

姓名 部门 出票量
李四 运营一部 500
王五 运营二部 300

过滤逻辑:

  1. 识别表头,找到"部门"列的索引
  2. 逐行检查部门字段是否在用户可见部门列表中
  3. 不在列表中的行直接删除,剩下的返回

国际部门的数据行被物理移除了,大模型压根不知道有这些数据存在。


第4层:Prompt 补充引导(补充强化)

这一层本质上是第1层的延续------在系统 Prompt 里持续强调权限约束,防止大模型在多轮对话中"遗忘"。

为什么要单独拎出来说?因为 Prompt 这东西有个特点:上下文越长,前面的指令越容易被"稀释"。用户聊了十几轮之后,第1层注入的权限标签可能已经被挤到上下文的角落了。第4层相当于在系统 Prompt 层面再"加固"一次。

当然,它本质上还是软约束。真正兜底的是第2层和第3层。


落地效果

上线后跑了两个月,几个关键数据:

  • 权限拦截准确率:跨部门查询 100% 拦截,没有出现过越权泄漏
  • 误拦截率:< 1%(主要是新入职员工部门信息未同步,补数据后恢复)
  • 用户体感:大部分用户感知不到权限控制的存在,查询体验没有明显降级

一些踩坑经验

坑1:SQL 改写要注意注入顺序

如果原始 SQL 里已经有多个 WHERE 条件,改写时要注意 AND 的位置和括号。比如原始 SQL 有 OR 条件:

sql 复制代码
WHERE 状态 = '正常' OR 状态 = '试用'

如果直接追加 AND 部门 IN (...),由于 SQL 优先级,逻辑可能变成 (状态 = '正常') OR (状态 = '试用' AND 部门 IN (...))

我们的做法是:给原始 WHERE 子句加括号,再追加部门条件

sql 复制代码
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 接入企业系统的权限控制,欢迎评论区交流。

相关推荐
持敬chijing1 小时前
Web渗透之前后端漏洞-XSS漏洞原理攻击防御全流程
前端·安全·web安全·网络安全·网络攻击模型·安全威胁分析·xss
ZeroNews内网穿透1 小时前
NAS部署Hermes AI Agent + 零讯内网穿透,实现远程可管理的AI助手
人工智能·安全·ai·内网穿透
毕竟是shy哥1 小时前
Claude Code 接入 DeepSeek 保姆级教程,WSL/Linux 通用
linux·安装教程·codex·deepseek·claude code·openclaw
阿正的梦工坊1 小时前
【Rust】04-借用、引用与切片
java·数据库·rust
AOwhisky1 小时前
学习自测与解析:MySQL第五、六、七期核心知识点详解
运维·数据库·笔记·学习·mysql·云计算
持敬chijing1 小时前
Web渗透之SQL注入总结
sql·安全·web安全·网络安全·网络攻击模型·web
阿标在干嘛1 小时前
政策平台的推送系统:消息队列、定时任务、AB测试的工程实践
服务器·数据库·ab测试
Upsy-Daisy2 小时前
Hermes Agent 学习笔记 02:安装、配置与第一次运行
java·前端·数据库
guyoung2 小时前
BoxAgnts 工具系统(4)——Tool Trait 与并发上下文模型
rust·agent·ai编程