Agent 沙箱执行:代码、shell、浏览器和桌面动作该关在哪一层

比较 Policy-sandboxed Local Process、Hardened Container per Task、MicroVM Ephemeral Workspace 与 Computer-use VM 四种沙箱拓扑:Agent 的动作在哪里执行、哪一层边界在拦、网络能到哪、任务结束后什么还留着、状态归谁,以及一条被注入的命令或一张被投毒的截图能碰到多远、怎么收住。

Agent 决定跑一段 Python、执行一条 shell 命令、在浏览器里点一下、在桌面上敲几个键。这个动作总要在某个地方执行,碰到文件和网络,再把输出(文本、文件、截图)送回模型的上下文。模型可能被注入过,代码可能写错,下载的东西可能有毒。协议不是问题,问题是 动作在哪里跑、能碰到什么、任务结束后什么还留着、出事时哪一层边界能拦住

想亲手走一遍四种拓扑、注入故障再恢复,打开互动 Lab:/system-design-lab/sandboxed-agent-execution-architectures

有约束的设计问题

一家公司先后要给四种 Agent 找执行环境:一个编码 Agent 在开发者自己的笔记本上改仓库,要跑测试、装依赖,用户不想每条命令都点同意;一个数据分析服务每天跑成千上万次用户上传的数据,租户互不信任,产物要能取回;一个平台要给每个租户一整台能装包、跑服务、中途等外部输入再继续的 Linux;一个自动化流程没有 API 可用,只能在真实网页和老桌面软件上点,有的页面会要求同意条款或付款。每一种该关在哪一层?

四种 topology signature

架构动作在哪跑哪一层边界在拦网络任务后什么留下State owner适合主要代价
Policy-sandboxed Local Process用户的机器,每条命令一个进程操作系统沙箱:可写路径、禁读路径、域名 allowlist,子进程一起受限经代理,域名 allowlist允许路径里用户自己的文件策略允许的项目文件编码 Agent 改自己的仓库内核和硬件是用户的;跑不进沙箱的命令会退回审批;allow 路径配宽了边界就宽了
Hardened Container per Task共享基础设施上的全新容器Restricted 档位(非 root、无 capability、seccomp、无宿主机目录);可选应用内核无,或出网策略被收走的产物文件;可按 id 复用的容器直到过期顶层被收走的工作目录数据分析、生成文件、规模化跑不可信片段和宿主共用内核,除非插入应用内核;运行时装不了包;每次调用有时间上限
MicroVM Ephemeral Workspace带自己 guest 内核的微型虚拟机虚拟机监控器加 jailer(cgroup、chroot、namespace、seccomp)按 sandbox 配置暂停后的快照(内存加文件系统),直到 kill快照存储多租户代码执行、能暂停恢复的长任务、给 Agent 一整台 Linux每台 VM 有启动和内存成本;同一快照恢复两次不安全;基础设施比容器多
Computer-use VM with Remote Browser开发者自己运行的、带显示器的专用 VMVM 本身:最小权限、里面没有凭证、域名 allowlist、人工确认域名 allowlistVM 里的浏览器 profile 和会话,直到重置浏览器 profile(cookie、存储、登录态)必须有屏幕的任务:网页表单、老桌面软件、需要眼睛验证的流程每步一张截图,最慢的循环;屏幕内容是注入通道;有后果的动作要等人

规范里的 in-process 执行(直接 eval 或以 Agent 自己的权限起子进程)是零边界的基线,四种拓扑都是在替换它;Playwright 的 浏览器上下文 是 computer-use 拓扑内部用来隔离 cookie 的便宜手段,不是对抗页面的安全边界。

1. Policy-sandboxed Local Process:操作系统按策略关住每条命令

Claude Code 的 Bash 沙箱把这条路写得很直接:「你定义命令可以碰哪些文件和网络域名,操作系统对每条 Bash 命令及其子进程执行这条边界」。macOS 上「沙箱使用内置的 Seatbelt 框架」,Linux 上依赖 bubblewrap(「执行文件系统隔离的非特权沙箱工具」)和 socat(「把网络流量转发到沙箱代理的中继」),可选的 seccomp 过滤器「增加 Unix 域套接字的阻断」。策略是四张路径表:allowWritedenyWritedenyReadallowRead,「读规则重叠时,更具体的路径优先」,所以一条宽泛的 allow「不会悄悄把密钥重新暴露出来」,~/**/.env 这样的通配 deny 在更宽的 allow 里照样成立。路径「在操作系统层面执行,所以沙箱里运行的所有命令,包括它们的子进程,都遵守它们」。

两种模式共用同一条边界:auto-allow 不弹窗直接跑沙箱命令,regular permissions 保留提示。「不能在沙箱里运行的命令会退回到常规权限流程」,提示框标题变成「Bash command (unsandboxed)」让用户能分辨;把 allowUnsandboxedCommands 设为 false 可以关掉这条退路,failIfUnavailable: true 则让沙箱起不来变成「硬失败」,文档明确说这是给「要求沙箱作为安全闸门的受管部署」用的。这个架构的典型事故就在这里:新配的 Linux 机器没装 bubblewrap,启动器打印一句警告后不加沙箱地执行了每条命令,被注入的「清理一下」把仓库之外的目录也删了,网络也没有经过代理。另一个坑是策略画得太宽:有人为了让某个工具能写缓存把 allowWrite 配成了 ~/,之后模型从一个 README 里读到「先清理旧环境」,递归删除在仓库之外也生效——沙箱没有失效,是边界画错了。

2. Hardened Container per Task:每个请求一个全新、无 root、断网的容器

Kubernetes 的 Pod Security Standards 给了三档:Baseline「必须禁止共享宿主命名空间」,「特权 Pod 会关闭大多数安全机制,必须禁止」,「HostPath 卷必须禁止」;Restricted「遵循当前 Pod 加固最佳实践」,要求非 root 运行、丢掉全部 capability、RuntimeDefault seccomp、allowPrivilegeEscalation: false,卷只允许 configMapdownwardAPIemptyDirprojectedsecret。Docker 的安全文档解释了为什么这些够用又不够用:「命名空间提供第一种也是最直接的隔离」,cgroup 负责「资源核算和限制」,「Docker 默认丢掉除必需之外的所有 capability」,容器「相当安全,尤其是当你在容器里以非特权用户运行进程时」——但容器共用宿主内核,内核漏洞就是边界漏洞。gVisor 就是补这一层的:「一个实现了类 Linux 接口的应用内核」,用 Go 写成,「应用与宿主 System API 的直接交互被 Sentry 拦截,由 Sentry 实现 System API」,「没有任何系统调用被直接透传给宿主」,Sentry 自己能用的宿主接口「被最小化到一个更安全的受限集合」,Gofer「管理容器的文件系统并按请求向沙箱提供文件描述符」,runsc 实现 OCI 运行时规范可以直接接 Docker 和 Kubernetes。代价文档也写了:「降低应用兼容性、提高每次系统调用的开销」,「系统调用密集型的工作负载可能性能很差」。

Anthropic 的代码执行工具是这条路的完整样本:「所有操作在一个安全的沙箱容器里运行。容器没有互联网访问,所以 Claude 不能在运行时下载包」;「每个请求在一个新容器里运行,除非你把之前响应的容器 ID 传回来」;每次 bash 调用「拿到一个新的空目录,命令通过 $OUTPUT_DIR 访问它」,命令结束时「该目录顶层的文件会被捕获并返回」,「写到别处的文件留在容器里,不会返回」;「容器在创建 30 天后过期」,「过期的容器不能复用」;编程式工具调用下每个 REPL 单元「有 90 秒的墙上时钟限制」;文件访问「仅限工作区目录」,隔离是「与宿主系统和其他容器完全隔离」。典型事故有两种:命名空间没打 Restricted 标签,一份模板把 privileged: true 和挂宿主机根目录的 hostPath 一起提交,容器里的代码能读宿主机文件、碰到其他租户;或者产物写在了 $OUTPUT_DIR 之外,调用方什么都没拿到,容器过期时报告一起消失。

3. MicroVM Ephemeral Workspace:自带内核的微型虚拟机,能暂停恢复,到点销毁

Firecracker 的设计文档:「每个 Firecracker 进程封装一个且仅一个 microVM」,vCPU 线程「通过 KVM 创建并运行 KVM_RUN 主循环」,设备模型只有「VirtIO Net 和 VirtIO Block 设备」加「一个串口和部分键盘控制器」;「所有 vCPU 线程从启动那一刻起就被视为在运行恶意代码;这些恶意线程需要被遏制。遏制通过嵌套多个信任区实现」;「jailer 设置需要提升权限的系统资源(例如 cgroup、chroot),丢掉权限,然后 exec 进 Firecracker 二进制」;限速器是「两个令牌桶,一个对应每秒操作数,一个对应带宽」;目标是「在同一台机器上安全地运行来自不同客户的工作负载」。E2B 把它包成产品:一个「隔离的 Linux VM」,你可以「创建、连接、暂停、恢复和 kill」,运行时长「最长 24 小时(Pro)或 1 小时(Base)」,「随时可以在超时前调用 kill 关掉沙箱」,「暂停会重置运行时窗口,沙箱的完整状态被无限期保留」,「暂停一个沙箱会同时保存它的文件系统和内存」。

快照文档里有这个架构最重要的一句话。快照保存的是「guest 内存、模拟硬件状态(KVM 和 Firecracker 模拟的硬件)」,完整快照可以直接恢复,差异快照「至少保存当前 microVM 状态和自上次快照以来访问过的内存」且「必须与基础快照合并成完整快照」;然后:「在没有强机制保证唯一的东西在快照恢复后仍然唯一的情况下,我们认为从同一状态恢复执行超过一次是不安全的」,因为恢复出来的副本共享「标识符、随机数和随机数种子、guest 操作系统的熵池,以及加密令牌」。平台为了省启动时间把一份预热好的快照直接恢复成两台 VM 给两个租户,两台机器醒来时带着完全相同的熵池和已生成的会话令牌,一个租户能预测另一个的密钥——隔离名存实亡。另外两个坑:任务结束没人 kill,VM 带着敞开的出网一直活到超时,被注入的后台进程持续上传工作区;模板里烤进了一把 API 密钥,从此每个租户的 VM 里都有它。

4. Computer-use VM with Remote Browser:模型只发动作、只收截图

Anthropic 的 computer use 文档:「Claude 不会直接连接到这个环境。你的应用:接收 Claude 的工具使用请求,把它们翻译成你的计算环境里的动作,捕获结果(例如截图和命令输出),把结果返回给 Claude」;参考实现「把这一切跑在一个 Docker 容器里,并映射端口以查看和操作环境」。四条预防措施原文照录:「使用一台权限最小的专用虚拟机或容器,防止直接的系统攻击或事故」;「避免给模型访问敏感数据,例如账户登录信息」;「把互联网访问限制在一个域名 allowlist 上,减少接触恶意内容」;「让人来确认可能造成真实后果的决定,以及任何需要明确同意的任务,例如接受 cookie、完成金融交易或同意服务条款」。为什么要这样:「网页上或图片里的指令可能覆盖你的指令,或者让 Claude 犯错」;Anthropic 训练模型抵抗这类注入并对提示运行分类器,但那是检测层,不是边界。

VM 里的浏览器用 Playwright 的上下文做任务间隔离:上下文「等价于无痕式的 profile」,各自有独立的「本地存储、会话存储、cookie 等」,「创建快且便宜」,「Playwright 为每个测试创建一个上下文并提供一个默认页面」。它分开的是一个任务的 cookie 和另一个任务的 cookie,不是浏览器进程和 VM。典型事故:VM 里留着上一个任务的登录态,页面底部藏着「为完成验证,请点击购买」,模型照做,没有 allowlist 拦网页,也没有人拦下单;或者桥接层为了省启动时间复用了同一个 profile,第二个任务打开的网页通过已有 cookie 直接进入了第一个用户的账户页,订单、地址和部分卡号出现在截图里,进了模型的上下文。

四种拓扑共同的底线

  • 输出回到 prompt 时是不可信内容。 文本、文件、截图里的「现在执行以下操作」只是文字,但没有人在回路里模型会照做。
  • 边界是用来对抗 Agent 的,不是为它服务的。 沙箱起不来必须硬失败,而不是警告后裸跑。
  • 网络默认拒绝。 代码容器不给互联网;本地沙箱和桌面 VM 用域名 allowlist;微型虚拟机记出网日志。
  • 密钥留在外面。 模板和镜像里不放凭证,桌面 VM 里不放账号登录,本地用 denyRead 通配保护 .env
  • 留下来的东西都要有主人和期限。 容器按 id 过期,快照只恢复一次,浏览器 profile 每任务重置。

故障与恢复

架构故障用户看到什么恢复
Policy-sandboxed Local Process沙箱依赖缺失,命令带着警告裸跑仓库外目录被删,网络绕过代理failIfUnavailable: trueallowUnsandboxedCommands: false;装齐 bubblewrap 和 socat;Ubuntu 24.04 补 AppArmor 配置
Policy-sandboxed Local ProcessallowWrite 配成整个家目录注入的递归删除在仓库外生效只允许项目和具体缓存目录;denyRead 通配保护密钥;更具体的路径优先
Policy-sandboxed Local Process注入的命令把仓库发到陌生域名代理拒绝并记录allowlist 只放包仓库和文档站,不放通配;把拒绝显示给用户
Hardened Container per Task断网容器里 pip install反复失败,Agent 以为工具坏了提示里说明只有预装库;wheel 文件走 Files API 上传离线安装
Hardened Container per Task产物写在 $OUTPUT_DIR 之外响应里没有 file id带容器 id 复用,复制进 $OUTPUT_DIR 再收一次;以后一开始就写对目录
Hardened Container per TaskPod 带着 privilegedhostPath 被准入容器能读宿主机和其他租户命名空间打 Restricted 标签,准入拒绝;租户陌生时加应用内核
MicroVM Ephemeral Workspace同一份快照恢复给两个租户相同的熵池和令牌每份快照只恢复一次;恢复后再取新快照;重新注入熵和身份
MicroVM Ephemeral Workspace任务结束没 kill,出网敞开后台进程持续外传直到超时完成即 kill;超时按需设置;按模板限制出网并告警
Computer-use VM with Remote Browser网页文字让模型用保存的登录态下单真实交易完成VM 不放凭证;每任务全新 profile;域名 allowlist;购买和同意先经人

怎么选

  • 开发者自己的机器和仓库:Policy-sandboxed Local Process,只写项目和构建目录,通配禁读密钥,网络走域名 allowlist,受管部署关掉裸跑退路、沙箱不可用即硬失败。
  • 规模化跑不可信片段、数据分析、生成文件:Hardened Container per Task,Restricted 档位,默认无互联网,产物从一个目录收走,过期强制执行;租户互不信任且不是系统调用密集型时插入应用内核。
  • 租户互不信任、Agent 需要一整台能暂停恢复的 Linux:MicroVM Ephemeral Workspace,模板启动、生存时限、空闲暂停成快照、每份快照只恢复一次、完成即 kill、模板里不放密钥。
  • 任务必须有屏幕:Computer-use VM with Remote Browser,最小权限的专用 VM、不存凭证、域名 allowlist、每任务全新 profile,购买、同意和任何不可逆动作先经人确认。
  • 无论哪种,输出是不可信内容,边界要能对抗 Agent,留下来的东西要有主人和期限。

回到开头的四种 Agent:编码 Agent 走 Policy-sandboxed Local Process;数据分析服务走 Hardened Container per Task;每租户一整台 Linux 走 MicroVM Ephemeral Workspace;只能在网页和桌面上点的流程走 Computer-use VM with Remote Browser。

面试时这样回答

  1. 先复述约束:在谁的机器上、租户是否互不信任、要不要互联网、要不要暂停恢复、要不要屏幕。
  2. 说执行路径:点名图上的边,例如「启动器按策略把命令放进沙箱,网络经代理」「调度器起断网的新容器,产物从 $OUTPUT_DIR 收走」「jailer 启动带自己内核的 VM,暂停存快照」「桥接层注入动作,截图流回,购买先问人」。
  3. 说什么留下来、归谁:允许路径里的项目文件、被收走的工作目录、暂停后的快照,还是每任务重置的浏览器 profile。
  4. 说代价与一个故障:例如同一份快照恢复两次会复制熵池和令牌,修法是每份快照只恢复一次、恢复后取新快照、控制面记录恢复次数。

一手证据