沙箱不是权限模型:多智能体系统安全设计的关键分层

发布时间:2026/8/30 7:55:21
沙箱不是权限模型:多智能体系统安全设计的关键分层 多智能体系统Multi-Agent Systems正在从一个学术词变成工程现实。主 Agent 拆任务、子 Agent 执行、工具调用、Agent 之间互相传递中间结果这套协作模式已经被写进了不少 AI 应用的产品架构里。但安全设计却常常被简化成一句话把 Agent 的代码放进沙箱里跑就行了。如果你也这么想这篇文章值得读完。沙箱和权限模型是两件不同的事。沙箱回答的是代码运行在什么环境里权限模型回答的是某个主体能不能执行某个操作。把沙箱当成权限模型等于默认只要出不去做什么都无所谓。在很多场景里这个假设是错的尤其是多个 Agent 协作时危险的往往不是突破沙箱逃逸到宿主机而是 Agent 在沙箱内部互相越权。这篇文章我会先讲清楚沙箱、权限模型和多智能体系统这三个概念的关系再从架构层面分析沙箱无法支撑授权需求的四个原因然后用一个虚构的越权场景把问题具象化最后给出一个最小可用权限模型的设计和代码示例。今天的内容不需要你掌握底层安全攻防但会给你一套在 Agent 项目里正确分层安全职责的思考框架。1. 为什么沙箱不是权限模型值得单独写一篇先说一个身边的观察。最近桌面端应用经常出现类似 ChatGPT is creating a sandbox needed to run on your computer 的提示。很多普通用户看到沙箱两个字第一反应是安全。在开发者社区里这种直觉也被放大了我做 Agent 应用把 Agent 的代码放在容器里把网络断掉只暴露白名单 API这样总安全了吧这个思路本身没有错隔离是安全的重要防线。但问题在于很多团队把它当成了安全的唯一防线甚至是权限管理的替代品。原因是多方面的。第一沙箱配置简单一个 Docker 容器、一个 gVisor 运行时、一组 seccomp 规则几分钟就能搭好效果也能在演示时立竿见影。第二权限模型设计起来麻烦需要在业务层面定义角色、资源、动作、操作条件还要做审计和策略下发短期内看不到直观效果。第三在单 Agent 场景下沙箱确实能挡住大部分违规操作因为边界只有内外两条。可一旦进入多 Agent 协作边界数量会从内外两层变成Agent 与 Agent、Agent 与工具、Agent 与数据、Agent 与外部服务的复杂网络此时仍然只依赖沙箱就已经失控了。更麻烦的是多智能体系统正在从研究走向生产。最近的一些研究热点比如 co-evolving multi-agent systems via interaction rewards已经在讨论多个 Agent 如何通过交互反馈共同进化。当 Agent 不再只是单个指令执行器而是会互相发送消息、共享内存、调用对方的工具时安全模型必须能把协作关系表达出来。沙箱做不到这一点它只是一个环境边界不是一套关系边界。所以这不是一道要不要用沙箱的选择题而是一道如何给安全做分层的必答题。沙箱是纵深防御里的一层权限模型才是多智能体系统安全的骨架。2. 三个概念一次性讲清楚沙箱、权限模型、多智能体系统2.1 沙箱Sandbox沙箱是一种运行环境隔离机制目标是限制程序能对系统资源产生的影响。常见的实现方式有操作系统级隔离Linux namespace、cgroups、seccomp主要限制进程对内核功能的访问。容器与安全运行时Docker、gVisor、Kata Containers通过更薄的 guest 内核或虚拟机边界隔离进程。语言运行时沙箱Java SecurityManager、Deno 的 permission API、WebAssembly 的 capability 机制在语言层做资源访问控制。网络隔离通过防火墙、代理或专用网络命名空间限制外部可达性。沙箱的核心能力是防逃逸和防扩散即使程序内部有恶意行为或 Bug也难以影响宿主机和其他租户。它是一种环境策略不关心谁在操作只关心这个环境允许什么操作。2.2 权限模型Permission Model权限模型是一套授权逻辑回答的问题是一个主体用户、服务、Agent被允许对某个资源执行某个动作。工程上最常见的权限模型有三类RBAC基于角色的访问控制把权限绑定到角色上给用户分配角色。例如财务 Agent角色可以调用查询账单工具但不能调用删除订单工具。ABAC基于属性的访问控制根据主体属性、资源属性、环境条件动态计算是否放行。例如只有运行在内部网络环境中的 Agent 且请求金额小于 10000 元时才允许发起转账。Capability能力 / 授权令牌每个操作持有者拥有一个不可伪造的令牌拥有令牌即可执行对应操作。类似 Unix 文件描述符或浏览器里的 OAuth scope。权限模型的关键点在于它是业务语义的映射。它知道合同 Agent和CRM Agent有什么区别知道读取客户信息和删除客户信息风险不同。这些语义如果不在授权系统里表达就会被沙箱忽略。2.3 多智能体系统Multi-Agent Systems多智能体系统指多个自治 Agent 通过消息传递、任务分工、协商或竞争来完成复杂目标的系统。在 LLM 应用里常见形态是一个 Orchestrator编排 Agent负责任务分解多个 Specialist专业 Agent分别处理不同的子任务每个 Agent 可以调用外部工具Tool / Function CallingAgent 之间可以通过共享消息总线、事件队列或直接调用来协作。从安全设计的视角看多智能体系统比单体 Agent 多出一层最难处理的关系Agent 之间的信任边界。在单体架构里进程边界就是安全边界在多智能体架构里安全边界必须落在每一个协作接口上。2.4 两者的关系隔离与授权是正交的两个维度维度沙箱权限模型回答的问题代码在什么环境里运行谁允许对什么资源做什么操作控制粒度环境级、进程级、系统调用级角色、资源、动作、条件级表达业务语义基本不表达核心就是业务语义处理主体身份不区分主体或仅区分进程区分用户、服务、Agent 等主体提供审计能力只能记录系统调用和网络行为可以记录业务操作和授权判断对付横向移动有限环境内部仍可自由行进能限制横向移动路径你可以把沙箱想象成一个隔离房间房间的墙很坚固外面的人进不来里面的人出不去。但房间里的人可以互相打斗可以乱翻文件可以给彼此传递危险物品。如果你只建了隔离房间却没有给房间里的每个人制定规则那么这个安全就只是隔离不是秩序。多智能体系统恰恰是一个房间里住着很多人的场景。3. 四个技术原因沙箱为什么撑不起多智能体系统的授权需求3.1 隔离不等于授权边界内外都有盲区沙箱构建的是一条内外边界。但多智能体系统的威胁模型不是单向的。第一次威胁可能来自外部攻击者但第二次威胁往往来自系统内部的某个被攻破的 Agent。当 Agent A 被目标构造的恶意输入诱导产生了一个越权行为沙箱只会看到这门没出符合环境限制完全不会判断Agent A 本来就不应该调用 Agent B 的资金工具。内部威胁、错误 Agent 行为、越权 Agent 通信这些在沙箱的视野里都是合法的因为它们没有突破环境边界。3.2 沙箱的粒度太粗表达不了业务权限沙箱能限制的是系统调用、文件读写、网络连接但它不知道读取 /finance/report.pdf和读取 /internal/employee_salary.xlsx在业务层意味着什么。你可以用 mount namespace 精确挂载某个目录却无法表达只有当用户是财务部成员时才允许读取该目录。权限模型则应该把资源抽象成业务对象。比如{ resource: customer_profile, action: read, subject: agent:crm, conditions: { scope: assigned_owner } }这种抽象沙箱做不到也不应该由它做。3.3 工具调用链路缺少主体身份传递现代 Agent 离不开工具调用。Agent 要执行 function call需要调用外部 API、数据库、文件系统。沙箱对工具的隔离方式是禁用/允许但没有化身和资源归属的概念。比如一个金融 Agent 平台主 Agent 调用子 Agent 来做客户流失分析。子 Agent 需要读取客户数据。如果只用沙箱限制你只能做成这个子 Agent 服务能读数据库但无法做到这个子 Agent 只能读本部门负责的客户数据。权限模型的核心价值就是能在调用链路的每一跳都做身份校验和授权判断。3.4 审计与追溯失败安全和合规要求谁做了什么合规审计要求你回答什么主体、在什么时候、对哪个资源、做了哪个操作、授权决策依据是什么。权限模型天然会产生这类审计事件主体身份请求动作决策结果允许/拒绝生效的策略 ID请求上下文沙箱日志记录的是系统调用例如open、execve、connect。这些日志几乎无法与业务操作映射。出了事故连是哪个 Agent 干的都很难定位。4. 一个典型的越权场景三个 Agent 和一层薄薄的沙箱我们先看一个虚拟教学场景用来把第 3 节的问题具象化。假设你设计了一个企业内部 AI 助手平台有三个 Agent财务 Agent能读取员工报销数据、账单数据调用支付工具的只读接口。合同 Agent能读取合同文件调用签约 API。CRM Agent能读写客户信息、商机信息。为了安全你做了两组防护把三个 Agent 分别部署在独立的容器里每个容器使用只读文件系统禁止外部网络。通过 API 网关给每个 Agent 只暴露必要的内部接口。从外部看这套架构确实很安全。攻击者不能从公网直接访问数据库Agent 也无法连到外部网络。但你忽略了一个关键点这三个 Agent 之间是通过内部消息队列相互通信的。现在攻击者构造了一个精心设计的 Prompt让 CRM Agent 进入一种被误导的状态。CRM Agent 的构造函数里有一个功能当你需要更新客户信息时可以把变更事件发送到内部消息总线。这个功能本意是让 CRM Agent 通知其他 Agent 客户资料变更。攻击者利用这个入口让 CRM Agent 在消息载荷中附加了一个不存在的合同审批指令。消息到达合同 Agent。合同 Agent 没有自己的权限检查机制它只信任消息总线里收到的事件。于是它执行了推进新的合同版本并调用签约接口。一秒钟后一个本不该存在的合同被签了。这个场景里容器沙箱、网络隔离都完好无损没有系统调用越界没有逃逸。出问题的恰恰是沙箱管不到的部分Agent 之间的消息没有身份验证、没有授权范围、没有动作校验。这就是沙箱是隔离不是权限模型最直观的例子。不需要攻破容器只要攻破一个 Agent 的 Prompt 上下文横向移动就可以利用 Agent 间通信链路完成。5. 最小可用的 Agent 权限模型设计如果不想再靠所有 Agent 关在一个大房间来保证安全你需要给权限模型一个明确的架构位置。下面是一个最小可用的设计包含五个组件5.1 主体Subject每个 Agent 必须拥有一个稳定身份。生产环境建议用服务身份证书或 JWTJWT 里至少包含{ agent_id: agent:crm, agent_type: specialist, team: sales, iss: agents-platform, iat: 1735689600, exp: 1735776000 }身份是权限判断的前提。没有稳定身份后面所有策略都无法绑定。5.2 资源Resource资源是 Agent 能操作的对象。多智能体场景里资源不仅是文件或数据库表还包括另一个 Agent 的能力。比如合同 Agent 签约能力应该被视为一个受保护资源调用方需要显式授权。5.3 动作Action对资源的具体操作例如read、write、call、approve、invoke。在 Agent 场景里最常见的是invoke即调用一个 Agent 或工具。5.4 策略Policy策略是授权规则。建议用声明式策略方便审计。下面是一个 ABAC 策略示例# 策略文件/policies/contract-signing.yaml apiVersion: authz.agent-platform/v1 kind: Policy metadata: name: contract-agent-sign-policy spec: effect: allow subject: allOf: - type: agent agentId: agent:contract resource: type: capability value: contract:sign action: invoke conditions: environment: internal amountLimit: 100000这条策略的含义是只有agent:contract可以在内部环境中调用合同签约能力且金额不超过 100000 元。5.5 决策点Decision Point所有工具调用和 Agent 间消息发送都必须经过一个统一的决策点。决策点的职责是接收请求主体、资源、动作、上下文加载策略计算授权结果返回允许或拒绝并记录审计日志。它就像微服务里的 API 网关只不过它管的是 Agent 内部的业务操作。6. 代码实战工具注册表 权限校验 沙箱隔离下面用一个简化但完整的代码链路演示沙箱 权限模型如何配合。6.1 定义权限策略先定义一份权限策略 JSON作为决策点的输入{ policyId: policy-001, name: crm-agent-customer-read, effect: allow, subject: { agentId: agent:crm }, action: read, resource: { type: data, name: customer_profile }, conditions: { dataScope: assigned_owner } }同时定义一条拒绝策略如果不命中任何 allow默认拒绝。这就是安全默认default deny。6.2 权限校验函数接下来是核心代码。这个函数会拦截所有 Agent 的工具调用请求先查身份再查策略。# 文件路径src/permission/authorizer.py from dataclasses import dataclass from typing import Optional dataclass class AuthRequest: subject_agent_id: str action: str resource_name: str context: dict class Authorizer: 极简权限决策点默认拒绝命中 allow 策略才放行。 def __init__(self, policies: list[dict]): # 生产环境请从远程策略服务加载不要硬编码在代码里 self._policies policies def check(self, req: AuthRequest) - bool: for policy in self._policies: if not self._subject_matches(policy, req): continue if not self._action_matches(policy, req): continue if not self._resource_matches(policy, req): continue if not self._condition_matches(policy, req): continue return True return False def _subject_matches(self, policy: dict, req: AuthRequest) - bool: subj policy.get(subject, {}) if agentId in subj: return subj[agentId] req.subject_agent_id return False def _action_matches(self, policy: dict, req: AuthRequest) - bool: return policy.get(action) req.action def _resource_matches(self, policy: dict, req: AuthRequest) - bool: res policy.get(resource, {}) return res.get(name) req.resource_name def _condition_matches(self, policy: dict, req: AuthRequest) - bool: conds policy.get(conditions, {}) for key, expected in conds.items(): if req.context.get(key) ! expected: return False return True代码里有一个关键决策check方法默认返回False。这意味着即使你只加载了一组策略没有显式 allow 的操作也一律拒绝。这是权限模型最重要的工程习惯默认拒绝而不是默认放行。6.3 在工具注册表中接入权限校验有了 Authorizer下一步是让所有工具调用经过它。这里用装饰器做一个工具权限包装器# 文件路径src/tools/registry.py import functools from src.permission.authorizer import Authorizer, AuthRequest class ToolRegistry: 工具注册表统一注册 Agent 可用的工具并在调用前做权限检查。 def __init__(self, authorizer: Authorizer): self._tools {} self._authorizer authorizer def register(self, tool_name: str, resource_name: str, action: str invoke): def decorator(func): functools.wraps(func) def wrapper(subject_agent_id: str, *args, **kwargs): req AuthRequest( subject_agent_idsubject_agent_id, actionaction, resource_nameresource_name, contextkwargs.get(context, {}) ) if not self._authorizer.check(req): raise PermissionError( fagent {subject_agent_id} is not allowed to f{action} {resource_name} ) return func(*args, **kwargs) self._tools[tool_name] wrapper return wrapper return decorator registry ToolRegistry(Authorizer([ { policyId: policy-001, name: crm-agent-customer-read, effect: allow, subject: {agentId: agent:crm}, action: read, resource: {name: customer_profile}, conditions: {dataScope: assigned_owner} } ])) registry.register(get_customer_profile, resource_namecustomer_profile, actionread) def get_customer_profile(customer_id: str, context: dict None): # 真实实现从这里开始查数据库、调用内部服务等 print(freading customer profile: {customer_id}, context scope: {context.get(dataScope)}) return {customer_id: customer_id} registry.register(delete_customer_profile, resource_namecustomer_profile, actiondelete) def delete_customer_profile(customer_id: str, context: dict None): # 这个工具没有对应的 allow 策略正常情况应该被拒绝 print(fdeleting customer profile: {customer_id}) return {deleted: True}运行下面的测试代码python -m src.tools.test_registry# 文件路径src/tools/test_registry.py from src.tools.registry import registry, get_customer_profile, delete_customer_profile try: result get_customer_profile( CUST-001, context{dataScope: assigned_owner} ) print(允许结果:, result) except PermissionError as e: print(被拒绝:, e) try: result delete_customer_profile(CUST-001) print(允许结果:, result) except PermissionError as e: print(被拒绝:, e)预期输出reading customer profile: CUST-001, context scope: assigned_owner 允许结果: {customer_id: CUST-001} 被拒绝: agent agent:crm is not allowed to delete customer_profile你应该能看到第二个调用被PermissionError拦截。这只是权限校验链路的最小演示但它证明了关键能力工具能不能被某个 Agent 调用不再由环境允不允许决定而是由策略决策点决定。6.4 沙箱仍然要保留隔离层是权限模型的补充有了权限模型不等于可以撤掉沙箱。权限模型管业务授权沙箱管底层系统面。以容器为例合理的做法是# 文件路径deploy/agent-container.yaml # 示例使用 Docker Compose 结构重点是隔离配置 version: 3.8 services: agent-crm: image: agent-platform/agent-crm:latest read_only: true tmpfs: - /tmp security_opt: - no-new-privileges:true cap_drop: - ALL cap_add: - NET_BIND_SERVICE networks: - agent-internal environment: - AGENT_IDagent:crm deploy: resources: limits: memory: 512M cpus: 0.5 authz: image: agent-platform/authz-policy:latest networks: - agent-internal在这个配置里read_only: true防止 Agent 容器写入根文件系统。cap_drop: ALL删除所有 Linux capabilities只添加网络绑定所需的一项。no-new-privileges: true防止特权提升。独立网络agent-internal限制通信范围。这些配置负责的边界是Agent 进程无法破坏宿主机环境无法随便读写目录无法向外网发起连接。 而第 6.2 和 6.3 节的权限模型负责的边界是Agent 进程无法调用它无权调用的工具和资源。 两者配合才构成完整的纵深防御。运行验证方面你不需要真正部署 Kubernetes可以先在本地用 Docker Compose 启动这些容器观察 Agent 是否能正常启动、是否能通过 HTTP 调用权限服务。失败时优先查找容器日志和 Authorizer 的审计日志。7. 常见误区与排查思路这里我把多智能体系统安全设计里最常见的几个问题整理成表格方便对照排查。问题现象可能原因排查方式解决方案所有 Agent 都在沙箱里仍然发生越权操作沙箱只隔离了系统级资源没有限制 Agent 间消息和工具调用查看消息总线的调用记录定位哪一个 Agent 发起了哪一个调用为 Agent 间通信增加身份认证和授权策略权限检查代码散落在各 Agent 内难以维护没有统一决策点每个 Agent 自己实现了一份权限判断搜索代码库中的权限判断逻辑确认是否有多处重复实现收敛到统一 Authorizer所有工具调用走同一个检查入口新加入的策略不生效策略文件被硬编码在服务里变更后服务未重新加载检查策略加载时间查看配置中心是否生效将策略放入配置中心或策略服务支持热更新和版本管理生产环境误放行高风险操作设置成了默认允许只有黑名单规则检查 Authorizer 的 check 函数返回逻辑确认默认值是否为拒绝改为 default deny并补充显式 allow 策略审计日志里无法定位到具体 Agent日志只记录了 IP 和进程 ID没有记录 agent_id查看日志字段是否包含主体身份在 AuthRequest 中强制携带 agent_id并写入审计日志Agent 间消息被信任未做校验消息总线只做了传输加密没有做业务层授权检查消息消费者的处理逻辑确认是否验证了消息来源和 action 范围对每个消息增加来源身份和授权 token消费者侧再校验一次这里特别提醒一个容易忽略的点权限校验必须贴近实际执行点而不是只在校验层做一次。一个 Agent 从消息队列里拿到的任务指令到实际调用工具之间可能经过 Prompt 组装、参数改写、上下文注入。如果只在入站层校验内部处理阶段依然可能产生新的越权调用。因此哪怕做了统一决策点也最好在所有工具注册表入口再做一层拦截。8. 最佳实践沙箱和权限模型的正确组合8.1 把权限模型当一等公民而不是事后补丁在多智能体应用的架构文档里权限模型应该和 Agent 编排、工具调用、消息路由放在同一个设计阶段。最理想的做法是新建任何一个 Agent 或工具时同时提交一份策略变更否则不允许上线。这和先有 API再有调用方是同一个工程纪律。8.2 严格使用最小权限原则给每个 Agent 分配身份和权限时只授予它完成业务任务所需的最小操作集。一个读取客户画像的 Agent不需要删除客户权限一个做合同归档的 Agent不需要发起对外签约权限。最小权限不仅降低恶意攻击的影响也能减少 Agent 因 Prompt 误导而产生的误操作范围。8.3 统一身份统一审计让所有 Agent 使用平台级的身份体系避免每个 Agent 内部各自维护一套用户/角色。JWT 与服务到服务的 mTLS 可以结合使用外部请求先做 TLS 认证再解析 JWT 得到 agent_id内部工具调用则直接使用 agent_id 作为主体。所有授权决策必须写审计日志字段至少包含请求时间主体 agent_id资源类型和名称动作决策结果命中的策略 ID请求上下文摘要8.4 沙箱照做但别让它替你思考业务规则沙箱隔离是底线防御不能省略。生产环境建议使用只读根文件系统。删除所有非必要 Linux capabilities。网络流量默认阻断按需放行。为每个 Agent 设置独立的资源配额和网络命名空间。如果对隔离强度要求很高可以考虑 gVisor 或 Kata Containers 这类更接近虚拟机的隔离方案。沙箱的作用是降低代码执行层面的风险权限模型的作用是降低业务决策层面的风险。二者不是替代关系而是两个正交方向上的防护。8.5 策略要支持版本化和灰度发布权限策略是线上系统的高敏感配置。一次错误的策略发布可能让所有 Agent 的调用全部失败也可能把原本不该放行的操作放开。建议策略使用独立的版本管理每次变更走评审流程并且支持灰度发布。可以先在一小部分 Agent 上应用新策略观察通过率和拒绝率再逐步扩大范围。8.6 定期做威胁建模演练多智能体系统不是静态系统。工具会新增、Agent 会调整职责、外部 API 会变化。建议每个迭代至少做一次威胁建模重点看三类问题新增工具是否纳入了权限模型Agent 之间的新消息类型是否做了授权检查是否还存在默认放行的路径这类演练的价值在于可以把安全设计从静态的代码审查变成动态的架构治理。9. 总结与下一步今天这篇文章的核心判断可以压缩成一句话沙箱管环境权限模型管行为多智能体系统的安全不能靠隔离环境来包办一切。我们用概念对比解释了沙箱和权限模型的本质区别用四个技术原因说明沙箱为什么撑不起多智能体场景的授权需求又用一个越权场景演示了沙箱完好但业务越权是怎么发生的。最后给出的权限校验代码链路是一个可以用最小成本接入现有 Agent 平台的参考实现。如果你正在设计多智能体应用下一步建议这样做先梳理当前系统中 Agent 能触达的全部工具和消息通道标记出哪些调用没有经过授权检查然后从最敏感的工具开始接入统一决策点。权限模型不需要一开始覆盖全部场景但必须在架构上留出位置。等到 Agent 数量上去了协作关系复杂了再回头补安全设计成本和风险都会成倍增加。如果你想继续深入这个概念可以沿着两个方向学习一是 ABAC 策略的语言设计和权限决策引擎的工程实现二是服务间身份认证机制比如 SPIFFE/SPIRE 和 mTLS。能理解隔离和授权之间的分工说明你已经开始用架构师的视角看待 Agent 系统了。这点认知比多写几个 Agent 编排脚本重要得多。