
从工具不可见到数据不越权:AgentScope工具权限的三层防线

前置阅读:《若依框架和阿里 AgentScope 的权限封装》
上一篇主要讨论如何把若依的角色权限接入 Agent 工具体系,重点解决“无权限用户不应该在模型上下文中看到 Tool Schema”,以及为什么不能只依赖工具执行阶段的 @PreAuthorize。本文继续向下拆解,给出一套覆盖工具可见性、调用决策和业务数据范围的三层权限模型。
一、为什么 Agent 工具权限不能只做一次校验
传统 Web 接口的权限控制通常发生在 Controller 或 Service 执行之前:
@PreAuthorize("@ss.hasPermission('inventory:stock:query')")
但 Agent 的执行链路比普通 HTTP 请求更长:
用户问题
→ Agent 获取 Tool Schema
→ 大模型选择工具
→ Agent 发起 Tool Call
→ Java 工具方法执行
→ Service 查询业务数据
→ 结果返回大模型
如果只在 Java 方法执行前校验权限,会留下三个不同的问题:
这三个问题对应三个不同的安全边界,不能用一个注解或一次拦截全部替代。
二、三层权限模型
一套完整的 Agent 工具权限体系,可以拆分为以下三层:
| 第一层:工具可见性 | 这个角色对应的 Agent 是否拥有该工具 | Tool Schema 发送给模型之前 | 保留或移除 Tool Schema |
| 第二层:调用决策 | 当前这一次工具调用是否允许执行 | 每次 Tool Call 执行之前 | ALLOW、DENY、ASK |
| 第三层:数据权限 | 工具执行后允许访问哪些业务数据 | Service、Mapper 或查询条件构建阶段 | 本人、本地区、本部门或跨区域数据 |
三层权限分别保护不同对象:
第一层保护模型上下文
第二层保护工具执行入口
第三层保护真实业务数据
三、第一层:角色与 Agent 的工具可见性
第一层解决的是:
当前角色对应的 Agent,究竟应不应该拥有这个工具?
假设系统中存在三个工具:
query_inventory 查询库存
allocate_spare_part 调拨配件
delete_maintenance 删除维修记录
普通维修工可能只拥有 query_inventory,区域管理员拥有查询和调拨工具,系统管理员才拥有删除工具。
在模型推理之前,应根据当前用户的角色与权限生成本轮可见工具集合:
普通维修工:
query_inventory
区域管理员:
query_inventory
allocate_spare_part
系统管理员:
query_inventory
allocate_spare_part
delete_maintenance
没有授权的工具不仅不能执行,而且不应该把以下内容发送给模型:
- 工具名称;
- 工具用途描述;
- 参数名称;
- 参数 JSON Schema;
- 可能暴露内部业务能力的示例。
这一层属于“能力披露控制”。它能够减少模型上下文噪声,也能降低提示词注入诱导模型尝试越权工具的风险。
需要特别注意:隐藏 Tool Schema 不等于完成了全部安全控制。模型不可见只是第一道门,工具执行入口仍然需要独立校验。
四、第二层:AgentScope Permission System 的调用决策
AgentScope Java 提供了 io.agentscope.core.permission 权限系统。根据官方文档,Permission System 会拦截 Agent 的每一次工具调用,并返回三种决策之一:
- ALLOW:允许执行;
- DENY:拒绝执行;
- ASK:暂停执行并询问用户是否确认。
这意味着同一个已经对模型可见的工具,不同调用仍然可以得到不同结果。
例如,维修工拥有配件调拨工具,但不同参数代表不同风险:
查询本地区配件库存 → ALLOW
调拨一个普通配件 → ASK
跨区域调拨高价值配件 → DENY
1. Permission System 的三个判断来源
官方权限系统综合以下三个组件进行决策。
Rules
PermissionRule 针对具体工具及调用模式配置 ALLOW、DENY 或 ASK。
规则可以在 PermissionContextState 中预先配置,也可以在用户确认 ASK 时接受建议规则,动态加入当前权限上下文。
PermissionContextState permissionContext =
PermissionContextState.builder()
.mode(PermissionMode.DEFAULT)
.addAllowRule(
"query_inventory",
new PermissionRule(
"query_inventory",
null,
PermissionBehavior.ALLOW,
"rolePolicy"))
.addAskRule(
"allocate_spare_part",
new PermissionRule(
"allocate_spare_part",
null,
PermissionBehavior.ASK,
"rolePolicy"))
.addDenyRule(
"delete_maintenance",
new PermissionRule(
"delete_maintenance",
null,
PermissionBehavior.DENY,
"rolePolicy"))
.build();
这里的 ruleContent 不一定只能匹配工具名称。工具可以通过 matchRule() 对实际调用参数进行匹配,从而实现“同一工具、不同参数、不同决策”。
Mode
PermissionMode 决定没有命中显式规则时的默认行为。
| DEFAULT | 未明确允许的调用进入确认流程 | 默认安全策略 |
| ACCEPT_EDITS | 自动允许安全范围内的编辑操作 | 用户在线的开发场景 |
| EXPLORE | 允许只读,拒绝写操作和命令 | 只读探索、规划 |
| BYPASS | 默认放行,但拒绝规则和危险检查仍有效 | 完全可信沙箱 |
| DONT_ASK | 将所有 ASK 转换成 DENY | 定时任务、无人值守流程 |
对于没有实时交互界面的后台任务,不应该让 ASK 无限等待。此时可以使用 DONT_ASK,把需要确认的调用安全降级为拒绝。
Built-in Checks
工具还可以通过 ToolBase#checkPermissions,基于本次真实输入进行动态风险分析。
它适合检查无法仅靠静态角色表达的风险,例如:
- 操作目标是否属于危险目录;
- 调拨数量是否超过阈值;
- 是否涉及高价值配件;
- 目标设备是否处于锁定状态;
- 当前调用是否会产生不可逆副作用。
官方文档强调,工具自身的危险检查属于运行时检查,不应被普通规则或模式绕过。
2. ASK 不是弹一个确认框那么简单
ASK 表示 Agent 的本次执行被挂起,系统需要保存:
- 用户身份;
- 会话身份;
- Tool Call 编号;
- 工具名称;
- 工具参数;
- 风险说明;
- 建议规则;
- 确认有效期。
用户确认时,必须恢复原来的调用,而不是重新让模型生成一次工具参数,否则确认的内容和最终执行的内容可能不一致。
确认页面还应该明确展示:
准备执行的工具:allocate_spare_part
调拨配件:智能马桶主控板
调拨数量:2
来源区域:华东一区
目标区域:华东三区
风险提示:跨区域调拨
用户确认的是一笔确定的操作,而不是模糊的“是否允许 Agent 继续”。
五、第三层:工具背后的业务数据权限
第三层解决的是:
用户拥有这个工具,也允许执行这次调用,但他最终可以看到哪些数据?
以库存查询为例,普通维修工和区域管理员都拥有:
inventory:stock:query
他们也都可能被 Permission System 判定为 ALLOW,但数据范围并不相同:
普通维修工:
只能查询所属地区的库存
区域管理员:
可以查询管辖区域内多个地区的库存
总部管理员:
可以查询全国库存
因此,数据权限必须在业务 Service 或数据库查询条件中落实:
Set<Long> readableRegionIds =
dataScopeService.getReadableRegionIds(currentUserId);
return inventoryService.getInventory(
request.getProductId(),
readableRegionIds);
查询条件必须取自服务端可信身份,而不能直接相信大模型生成的 regionId。
错误方式:
return inventoryMapper.selectByRegionId(toolRequest.getRegionId());
正确思路:
模型传入目标区域
→ 服务端计算当前用户可访问区域
→ 判断目标区域是否在授权集合内
→ 带数据范围条件查询
即使模型伪造了其他区域编号,也无法越过服务端的数据范围限制。
六、三层权限如何协同
以“区域管理员跨区域调拨配件”为例:
第一层:工具可见性
区域管理员拥有 allocate_spare_part
→ Tool Schema 可以进入模型上下文
第二层:调用决策
当前调用属于跨区域调拨
→ Permission System 返回 ASK
→ 用户确认后才继续
第三层:数据权限
校验来源区域和目标区域是否都在管理员管辖范围
→ 在范围内执行
→ 超出范围拒绝
任何一层失败,都必须停止调用:
Tool 不可见
OR Permission = DENY
OR 用户拒绝 ASK
OR 数据范围不满足
→ 不执行工具
七、角色权限矩阵示例
| 三方维修工 | 查询维修手册、查询本人维修单 | 查询 ALLOW,写操作 DENY | 本人被派发的工单 |
| 普通维修工 | 查询库存、申请配件 | 查询 ALLOW,申请 ASK | 所属地区 |
| 区域管理员 | 查询库存、配件调拨 | 查询 ALLOW,跨区调拨 ASK | 管辖区域 |
| 总部管理员 | 全部管理工具 | 普通操作 ALLOW,高风险操作 ASK | 全国 |
| 无人值守任务 | 仅白名单工具 | DONT_ASK,其余拒绝 | 任务配置的数据范围 |
这个矩阵中的三列不能合并:
- Tool Schema 决定模型“知不知道有这个能力”;
- Permission 决定本次调用“能不能做、是否要问”;
- 数据范围决定最终“能操作哪些业务对象”。
八、工程落地时最容易踩的坑
1. 只隐藏工具,不校验执行入口
攻击者可能绕过模型入口直接构造 Tool Call,因此执行阶段仍需权限控制。
2. 只做 ALLOW / DENY,忽略 ASK
完全放行和完全拒绝之间需要有人机协同边界。配件调拨、删除记录、批量修改等操作更适合 ASK。
3. 把数据权限写进 Prompt
Prompt 只能提示模型,不是安全边界。地区、部门、用户等数据范围必须由后端身份和查询条件决定。
4. 共享可变权限上下文
如果 Agent 是单例,不能把某个用户的动态权限直接修改到全局共享 Toolkit 或全局规则集合中,否则并发请求可能串权。
权限状态至少应按用户和会话隔离:
(userId, sessionId) → PermissionContextState
5. ASK 恢复时重新生成参数
确认前后的工具名称和参数必须一致。建议保存原始 Tool Call,并在确认后恢复执行。
6. 把 BYPASS 当作生产默认值
BYPASS 更适合完全可信的隔离环境。生产系统应从最小权限开始,根据明确规则逐步放行。
九、推荐的安全原则
可以用一句话概括三层工具权限:
模型只看见应该看见的工具,每次调用都经过明确决策,工具最终只能访问授权范围内的数据。
落地时建议遵循:
十、总结
Agent 工具权限不是一个简单的“能不能调用”问题,而是一条完整的安全链路:
角色与 Agent 工具授权
→ Tool Schema 可见性
→ Permission System 调用决策
→ 用户确认
→ 业务数据权限
→ 审计记录
第一层让无权限工具不进入模型上下文;第二层使用 AgentScope Permission System 对每次调用执行 ALLOW / DENY / ASK 决策;第三层在真实业务查询中落实地区、部门、用户等数据范围。
三层共同工作,才能真正实现工具能力最小化、调用过程可控制、业务数据不越权。
网硕互联帮助中心


评论前必须登录!
注册