这次我们不聊某个具体的 AI 模型,而是聊一个越来越多人在多智能体系统(Multi-Agent System)里会遇到的安全设计问题:把 Agent 丢进沙箱,是不是就等于做好了权限控制?
答案很直接:不是。沙箱解决的是“隔离”,权限模型解决的是“授权”。两者能互相配合,但不能互相替代。很多团队在做多 Agent 编排、Agent 工具调用、Agent 自动执行任务时,只关注了沙箱环境,却没有给每个 Agent 分配真正的身份、权限边界和审计策略,结果就是:代码跑在沙箱里,但沙箱里的 Agent 依然可以为所欲为。
这篇文章会拆开讲清楚三点:
- 沙箱在多智能体系统里到底解决了什么问题,解决不了什么问题。
- 一个完整的多 Agent 权限模型应该长什么样。
- 实际落地时,怎么把沙箱和权限模型组合成一套可执行的安全架构。
如果你正在做 Agent 框架、接入大模型工具调用,或者要给自动化任务加安全边界,这篇文章可以直接收藏。
1. 核心区别:沙箱是“碰不到”,权限模型是“能不能碰”
先厘清两个概念。
沙箱是一种运行时隔离机制。容器、虚拟机、Firejail、bubblewrap、gVisor、Firecracker、WebAssembly 运行时,本质都在做同一件事:限制一个进程能看到的资源边界。沙箱回答的问题是:“这个程序能访问文件系统、网络、系统调用吗?”它通过隔离手段来阻止越界访问。
权限模型是一种决策机制。RBAC(基于角色的访问控制)、ABAC(基于属性的访问控制)、DAC(自主访问控制)、MAC(强制访问控制),核心都在回答另一个问题:“这个主体对那个客体,能不能执行这个操作?”它关注的是授权关系,而不是物理隔离。
| 对比维度 | 沙箱 | 权限模型 |
|---|---|---|
| 核心问题 | 代码运行在什么样的隔离边界里 | 谁有权限对什么资源做什么操作 |
| 技术手段 | 容器、VM、seccomp、Landlock、AppArmor | RBAC、ABAC、策略决策点、访问控制列表 |
| 目标 | 缩小爆炸半径,防止越界读取或破坏 | 精确控制授权范围,支持不同主体差异化权限 |
| 能替代权限模型吗 | 不能 | 不能 |
| 多 Agent 场景的局限 | 沙箱内 Agent 依然可能滥用所有可用权限 | 需要 Agent 身份、工具权限、数据权限、通信权限一起管控 |
多智能体系统里最危险的操作往往不是“绕过沙箱”,而是“在沙箱内获得了过高权限”。举例来说:
- 一个代码生成 Agent 被允许读取整个代码仓库。
- 一个任务分解 Agent 被允许调用所有外部 API。
- 一个文件处理 Agent 被允许写任意路径。
- 一个 Agent 可以读取另一个 Agent 的上下文或中间结果。
这些情况里,沙箱可能正常工作了,数据也没有逃出进程边界,但 Agent 已经在授权范围内做了不该做的事。这就是“沙箱不是权限模型”的直观体现。
2. 多智能体系统的安全边界为什么更难做
单 Agent 程序的安全边界相对好理解:一个进程,一套权限,把它隔离好就行。多 Agent 系统复杂度更高,原因在于:
2.1 多个主体,多个信任级别
不同 Agent 来自不同的代码路径,可能由不同团队开发,甚至可能来自第三方插件或外部模型。它们不应该共享同一套权限。一个负责文档解析的 Agent,和被授权调用支付 API 的 Agent,信任级别完全不同。
如果系统只提供一个沙箱,所有 Agent 进去后权限一致,那就等于没有做主体维度的权限隔离。
2.2 工具调用和资源访问是动态的
Agent 在执行任务时,工具调用路径不是预先写死的。同一个 Agent,这轮可能只读一个文件,下一轮可能被要求批量删除文件。权限模型不能只在启动时固定一次,需要在每次工具调用时动态评估。
2.3 Agent 之间存在委派和传递
编排型 Agent 会把任务委派给子 Agent,子 Agent 可能继续调用另一个 Agent。这带来一个经典问题:权限怎么传递?
假设编排 Agent 有读文件权限,它委派子 Agent 去读文件时,子 Agent 是否应该继承这个权限?如果继承,子 Agent 的权限边界就模糊了;如果不继承,任务可能无法完成。
这个问题的答案,不能靠沙箱给出来,必须由权限模型来设计。
2.4 上下文不一定是可信的
多 Agent 系统中,一个 Agent 的输入可能来自另一个 Agent 的输出。如果上游 Agent 被误导或注入恶意指令,下游 Agent 很难区分“用户指令”和“上游数据”。权限模型需要尽可能减少这种数据流交叉带来的风险,例如默认禁止高权限 Agent 直接把执行结果写回低可信区域。
3. 沙箱能做什么,不能做什么
沙箱仍然是安全架构里非常重要的一层,但它有明确边界。
3.1 沙箱能做的
- 隔离文件系统访问,防止 Agent 读取宿主机上的任意文件。
- 限制网络访问,阻止 Agent 主动外连不受信任的地址。
- 按系统调用维度限制行为,例如禁止
execve、禁止mount。 - 控制资源使用,防止单个 Agent 把 CPU、内存、磁盘占满。
- 降低一次入侵或一次误操作带来的破坏范围。
3.2 沙箱不能做的
- 区分“这个 Agent 可以调用工具 A,另一个 Agent 只能调用工具 B”。
- 判断“这条数据流是否允许从 Agent A 流向 Agent B”。
- 定义“用户对某个 Agent 授予了多大范围的操作权限”。
- 回答“这个请求是用户主动发起的,还是 Agent 自主决策发起的”。
- 记录“是谁在什么时间因为什么原因执行了什么操作”。
这些职责,必须由权限模型来完成。
| 能力 | 沙箱提供 | 权限模型提供 |
|---|---|---|
| 进程隔离 | 是 | 否 |
| 资源限制 | 是 | 否 |
| 主体身份区分 | 部分 | 是 |
| 操作级别授权 | 否 | 是 |
| 跨 Agent 通信控制 | 否 | 是 |
| 审计与追溯 | 弱 | 强 |
4. 多智能体系统权限模型的设计框架
一个可落地的多 Agent 权限模型,至少要覆盖五个维度。
4.1 主体身份
每个 Agent 必须有独立身份。这个身份不是简单的进程 ID,而是在权限系统里可被识别和引用的实体。
在多 Agent 系统里,Agent 身份可以是:
- 一个 service account。
- 一个 SPIFFE ID。
- 一个 JWT 声明的
sub。 - 一个企业内部的身份标识。
有了身份,才能做授权决策。没有身份,权限无从谈起。这是最容易被忽略的一步。很多实现把 Agent 当作“进程的一部分”,所有 Agent 共享一个服务身份,等于权限模型直接失效。
4.2 客体资源
客体是 Agent 要访问的资源,包括:
- 文件路径。
- 数据库表或记录。
- API 端点。
- 外部工具。
- 另一个 Agent。
系统里应该有一份清晰的资源清单,每种资源有明确的类型和访问方式。不能只写“允许访问文件”,要精确到“允许访问 /data/input 目录下的 README.md”。
4.3 操作集合
每个 Agent 能执行的操作要尽量缩小。常见操作包括:
- 读取。
- 写入。
- 追加。
- 删除。
- 执行。
- 调用。
- 发送。
最小权限原则在这里体现得最明显。不要给 Agent 一个“读写全部文件”的权限,除非它有明确理由。
4.4 约束条件
权限不是无条件生效的。需要支持按条件动态判断:
- 时间范围。
- IP 来源。
- 会话上下文。
- 任务类型。
- 数据敏感级别。
- 用户授权的临时范围。
ABAC 的典型场景就在这:同样是写文件操作,一个 Agent 在普通任务里可以写临时目录,在敏感任务里就必须经过二级审批。
4.5 审计记录
每次授权决策都应该被记录。多 Agent 系统里,操作链路很长,一个结果可能是多个 Agent 协作产生的。没有审计日志,出事之后无法回溯是哪一步出了问题,也无法判断是哪个 Agent 越权。
5. 落地架构:沙箱与权限模型如何组合
推荐的分层思路是:运行时隔离在最内层,权限决策在中间层,编排控制在最外层。
请求进入 -> 外层:编排控制,判断当前任务属于哪个用户、哪个会话 -> 中间层:权限决策,查询策略,判断 Agent 是否有权执行该操作 -> 内层:沙箱执行,在受限环境中运行代码 -> 输出:返回结果并记录审计日志5.1 三层职责边界
| 层级 | 核心职责 | 技术示例 |
|---|---|---|
| 编排层 | 任务分发、身份映射、会话上下文 | Agent 编排框架、任务队列 |
| 策略层 | 授权决策、权限评估、条件判断 | OPA、自研策略服务、RBAC/ABAC 引擎 |
| 沙箱层 | 进程隔离、资源限制、系统调用过滤 | gVisor、Firecracker、容器 + seccomp + AppArmor |
5.2 策略配置示例
下面给一份通用策略模板。实际字段名以你的策略引擎为准,但表达思路可以直接复用。
# 策略示例:根据不同 Agent 身份授予不同工具权限 policies: - name: "agent-a-read-only" subject: agent_id: "agent-a" action: "read" resource: type: "file" path: "/data/inputs/**" effect: "allow" constraints: time_range: "09:00-18:00" - name: "agent-b-tool-call" subject: agent_id: "agent-b" action: "call" resource: type: "tool" name: "search_engine" effect: "allow" constraints: max_calls_per_minute: 30 - name: "default-deny" subject: agent_id: "*" action: "*" resource: type: "*" effect: "deny"核心思路是:默认拒绝,显式放行。任何人、任何 Agent,在没有明确策略之前,不应该获得任何权限。
5.3 调用流程伪代码
在 Agent 执行工具调用时,建议在入口处做一次授权检查。示例逻辑如下:
def execute_tool_call(agent_id, tool_name, payload, user_context): # 1. 构造权限请求 permission_request = { "subject": {"agent_id": agent_id}, "action": "call", "resource": {"type": "tool", "name": tool_name}, "context": user_context } # 2. 调用策略决策点 decision = policy_engine.evaluate(permission_request) # 3. 决策结果为 false 时直接拒绝 if not decision.allowed: audit_logger.log( agent_id=agent_id, tool_name=tool_name, result="denied", reason=decision.reason ) raise PermissionDeniedError(decision.reason) # 4. 决策通过后,在沙箱环境中执行 result = sandbox_executor.run( agent_id=agent_id, tool_name=tool_name, payload=payload ) # 5. 记录执行结果 audit_logger.log( agent_id=agent_id, tool_name=tool_name, result="success", output_summary=result.summary() ) return result需要特别说明的是,这段伪代码只是为了表达“授权检查应该在沙箱执行前完成”这个顺序。生产环境要处理的东西更多,比如并发请求、缓存策略、策略热更新、失败重试等。
6. 工程落地的关键实践
6.1 每个 Agent 独立身份
不要把所有 Agent 都映射到同一个服务账号。独立身份是权限模型的基础。如果 Agent 框架不支持身份区分,后续的权限控制基本没法做。
可以这样分工:
- Agent 框架负责把任务和身份绑定。
- 策略引擎负责基于身份做授权判断。
- 沙箱负责身份对应的最小执行环境。
6.2 默认拒绝
策略配置里,最后一条永远是deny all。网络上没有可信的内网概念,Agent 之间的调用也要走同样的策略检查。
6.3 精确到工具和数据级别
不要写“允许访问全部文件”这种宽泛策略。多 Agent 系统里,工具和数据是最主要的资源对象。尽量做到:
- 文件按目录或 glob 模式精确匹配。
- API 按端点和 HTTP 方法匹配。
- 工具按名称和参数约束匹配。
- 数据库按表级别或行级别匹配。
6.4 权限传递要显式
Agent 委派子任务时,要在任务上下文中显式声明权限边界,而不是默认继承。推荐的做法是:
子 Agent 启动时,接收一个 scope 参数,里面包含本轮任务允许访问的资源列表。
{ "task_id": "task-001", "assigned_agent": "agent-c", "allowed_resources": { "files": ["/data/inputs/specs/**"], "tools": ["text_parser", "summary_generator"], "network": ["https://api.example.com/v1/clean"] }, "expires_at": "2025-08-01T00:00:00Z" }6.5 审计日志要带请求链路
多 Agent 协作的问题在于,你很难判断一个操作是用户主动触发的,还是某个 Agent 链式调用产生的。建议在日志里记录:
- 初始用户请求 ID。
- 当前 Agent 调用链。
- 上游 Agent 输出摘要。
- 授权决策结果。
- 沙箱执行环境标识。
这样出了安全事件,至少能快速定位是哪一段链路出了问题。
6.6 策略要支持动态更新
Agent 系统的任务变化非常快。策略更新不能每次重启服务。建议:
- 策略存在外部存储中,比如数据库或配置中心。
- 策略引擎支持热加载。
- 策略变更要留审计记录。
7. 常见误区与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| Agent 能访问未授权的目录 | 权限策略过宽,或沙箱挂载了宿主机目录 | 检查挂载配置和策略规则 | 按需挂载目录,收紧文件路径匹配 |
| 所有 Agent 权限相同 | 没有区分身份,全部映射到同一个 service account | 查看 Agent 身份绑定关系 | 为每个 Agent 分配独立身份 |
| 子 Agent 继承了父 Agent 的高权限 | 权限传递采用隐式继承 | 检查委派任务上下文 | 改为显式 scope 传递,按任务分配最小权限 |
| 工具调用没有经过授权检查 | 授权检查写在工具内部而不是调用入口 | 审查代码调用链路 | 在统一入口处增加策略决策点 |
| 审计日志查不到关键操作 | 日志没有覆盖授权失败场景 | 检查日志采集规则 | 将 allow 和 deny 都记录为结构化日志 |
| 策略修改后不生效 | 策略缓存未刷新 | 检查策略引擎缓存机制 | 增加版本号并支持热更新 |
这几类问题里,最常见的是“所有 Agent 共享一个身份”。这个坑一旦踩了,后面再加沙箱、加策略,效果都会打折。
8. 与工具链结合时的落地建议
8.1 Agent 工具调用协议层的权限控制
现在很多 Agent 框架开始标准化工具调用协议。无论你用的是 MCP 还是自研协议,都要在协议层加入权限检查。可以这样理解:
- 工具本身只负责执行。
- 工具网关负责认证和授权。
- 沙箱负责执行环境隔离。
- 审计服务负责全链路记录。
工具调用链路至少要有四段:Agent 发出请求 -> 网关检查身份 -> 策略引擎给出决策 -> 沙箱执行工具。
8.2 CI/CD 里的策略测试
权限策略也要纳入 CI 流程。可以设计一组自动化测试用例:
- 测试“无身份 Agent 调用工具”是否被拒绝。
- 测试“越权读取文件”是否被拒绝。
- 测试“子 Agent 默认继承权限”是否不符合预期。
- 测试“超时后临时授权是否失效”。
- 测试“策略更新后旧请求是否不受影响”。
这些用例不需要真实执行危险操作,只需要在策略引擎的模拟环境里跑一遍。
8.3 隐私与合规
多 Agent 系统处理用户数据时,还要考虑数据最小化和租户隔离。权限模型里的身份和资源,最好也包含租户维度。比如:
- 用户 A 的 Agent 不允许读取用户 B 的文件。
- 日志中的输入输出要做脱敏处理。
- 涉及人脸、声音、版权素材的 Agent 必须确认授权范围。
- 敏感操作要触发二次确认或人工审批。
9. 总结与下一步
“沙箱不是权限模型”,这句话的实质是:多智能体系统的安全能力,不能只靠运行时隔离来兜底。真正要做的是在隔离之上,建立一套包含身份、资源、操作、约束和审计的完整授权体系。
最先应该验证的三件事:
- 你的系统能否区分每个 Agent 的身份。
- 每个 Agent 是否只拿到了本轮任务所需的最小权限。
- 每次授权决策是否都被记录,且能沿着调用链回溯。
最容易踩的坑,是把所有 Agent 塞进同一个沙箱、共享同一个身份,然后以为安全已经做好了。更稳妥的路线是:先做默认拒绝,再逐步放行,最后把策略测试和审计日志补上。
后续可以考虑的方向,包括策略引擎选型、沙箱运行时对比、Agent 间信任模型设计、以及跨租户隔离。每一个方向,都比单纯加一层沙箱更值得投入时间。