Agent 的 sandbox 执行工程:从 firecracker 微内核到 browser-use 隔离的生产闭环
约 11 分钟3113 字2 次阅读

Agent 的 sandbox 执行工程:从 firecracker 微内核到 browser-use 隔离的生产闭环
一、问题的提出:为什么 sandbox 成了 Agent 工程的「最后一公里」
2026 年的 Agent 系统早已不是单纯的 prompt 编排。当一个 Agent 真的能在生产环境里读写文件、执行 shell、调用浏览器、跑 Python notebook、甚至向 MCP server 提交事务,它所面对的攻击面已经从「模型是否被 prompt injection」扩展到「一段被污染的网页内容能否逃逸到宿主内核」。我们看到的最严重的事故不是模型答错题,而是 Agent 在执行某一步「帮我打开这个链接总结一下」时,链接里的隐藏指令让一个 rm -rf 或者一个 curl | sh 直接落在了 Agent 进程的同一 uid 下,把生产数据库的快照一并带走。
这种事故有一个共同的结构特征:LLM 输出与系统调用之间没有任何物理边界。所谓 sandbox 不是一个产品,而是一组在不同信任层级之间逐步抬高调用代价的纵深防御栈。这篇文章要讨论的不是「firecracker 怎么用」或「docker run --read-only 怎么写」,而是把 sandbox 当作一个工程域来看:它的威胁模型分层是什么,每一层的失败模式分别是什么,主流实现(firecracker / gVisor / nsjail / wasm / playwright / e2b / modal)在四个关键维度(启动延迟、隔离强度、可观测性、生态成本)上各自的取舍点,以及一个生产级 Agent sandbox 体系应该长成什么样的拓扑。
我们要回答的核心问题是:给定一个 LLM 输出的 tool_call,我们要在多大代价下、用多少层防御、把它能造成的最坏后果压缩到「一段 Python 子进程读自己的临时文件然后退出」这个范围? 当我们把这道题答清楚,sandbox 才从「DevOps 的某个开关」变成 Agent 工程的「一等公民」。
二、形式化:sandbox 的四元组与四个不变量
我们用一个四元组描述一次 sandbox 执行:
E = (T, A, R, C)
T= 工具类型(tool type):读文件、写文件、执行 shell、启动浏览器、调用远程 MCP、长事务事务等。每一类工具的危险半径不一样。A= 攻击面(attack surface):被污染的内容能流经的通道,包括 stdin、网络出站、文件系统路径、IPC、env。R= 资源约束(resource budget):CPU、内存、wall time、文件大小、网络字节数。C= 可观测性契约(observability contract):执行前后必须留下什么审计痕迹,用于事后归因。
一次「合格的 sandbox 执行」需要满足四个不变量:
- 不可逃逸(non-escape):被污染的内容不能升级到执行它的进程之外,跨命名空间或跨内核的 syscall 必须被截断或拒绝。
- 可重放(replayability):同样的
(T, A, payload)应当能产生同样的最终文件系统状态与网络侧记录,除非被业务层的随机性显式覆盖。 - 可归零(zeroability):执行完成后,所有动态分配的临时资源(tmpfs、cgroup、tap 设备、cookie jar)必须能被一个原子动作回收,不留半挂状态。
- 可解释(explainability):任何被拒绝的 syscall、被截断的网络包、被沙箱化的进程死亡,都必须能映射回具体的策略规则与原始请求 id,便于线上复盘。
这四个不变量之间存在天然的张力:不可逃逸要求更重的隔离(firecracker > gVisor > nsjail > seccomp),而可重放要求更轻的状态(容器 < 进程 < VM)。所以工程上的取舍不是「选哪种沙箱」,而是「在哪一层用哪种沙箱,并接受它对应的代价」。下面我们按工具类型逐层展开。
三、第一层:shell 与本地进程的轻量沙箱——nsjail 与 seccomp 的工程真相
绝大多数 Agent 的第一步是「执行一段 Python」或者「跑一段 bash」。这一步的危险半径看似不大,但所有上层 sandbox 的能力边界都被这一层决定:如果你的「执行 Python」跑在宿主机 uid 1000 下,那么后续所有 VM 级隔离都只是把 1000 这个 uid 隔离掉,而不是把 root 隔离掉。
nsjail 是这个层面最贴近 Agent 工程实际的工具。它的本质是一个「用 Linux 命名空间 + cgroup + seccomp 把单个进程树关进笼子」的封装,相比 docker 它有三个 Agent 工程师会立刻爱上的特性。下面我们把这三个特性背后的工程动机展开讲清楚,因为它们直接决定了 nsjail 在 Agent 体系里的位置:
第一,「策略即配置」不是 nsjail 的副产物,而是它的核心设计目标。一个 .cfg 文件写清楚挂哪些 mount、限制哪些 syscall、是否允许网络,这种「声明式隔离」天然和 tool schema 对齐——tool registry 在注册一个 run_python 工具时,可以同时声明 sandbox_policy: "agent-strict.txt",于是安全策略和业务代码彻底解耦。当安全团队发现某个 CVE 需要紧急加一条 syscall 拒绝规则,他们只需要改 agent-strict.txt 一个文件,不需要碰任何业务 tool 的代码。这是 docker compose 在「运行时配置」层面长期缺失的能力。
第二,毫秒级启动不是优化出来的,是架构选择的副产品。nsjail 不创建完整 container(不拉 cgroups hierarchy、不启动 init、不挂载 overlay),它只是在 fork 之后立刻 unshare 命名空间 + 装载 seccomp filter,然后 exec 用户命令。这条路径的 syscall 数大概是 50-80 个,冷启动 < 30ms 是 Linux 内核 fork 的物理下界决定的,不是 nsjail 自己能再压的。这恰好是 Agent 场景的关键——LLM 一次推理可能要触发 5-10 次 tool_call,如果每次 tool_call 都要等 500ms 才能开始执行,整个 Agent 的响应延迟会被 sandbox 启动本身拖垮。nsjail 让 sandbox 对 Agent runtime 几乎是「无感开销」。
第三,天然兼容 stdin/stdout JSON RPC 这一点经常被忽视,但它实际上是 nsjail 在 Agent 生态流行的真正原因。Agent 的 tool_call 本质上就是一个 JSON over stdin,工具的 stdout 也需要结构化 JSON 返回。nsjail 把 stdin/stdout 当作 host-agent 通信通道,无需额外定义 IPC 协议,而 docker / podman 都需要通过 volume mount 或 sidecar 解决这个通信问题。这是为什么 OpenAI 的 code interpreter 早期版本、Anthropic 的 tool use 实验台都直接选用 nsjail 而不是 docker。
- 声明式隔离策略:一个
.cfg文件写清楚挂哪些 mount、限制哪些 syscall、是否允许网络。这种「策略即配置」恰好和 tool schema 对齐。 - 毫秒级启动:相比 firecracker 几百毫秒的 VM 启动,nsjail 是单进程 fork+exec 量级(实测在 8c16g 节点上冷启动 < 30ms)。
- 天然兼容 stdin/stdout JSON RPC:这是 Agent 工具调用最自然的协议形态,避免了 VM 级 sandbox 经常要解决的「host-agent 通信问题」。
但 nsjail 也有几个我们必须正面承认的弱点:
- 隔离强度有限:它共享宿主机内核,理论上存在内核逃逸的 CVE 风险。对 untrusted 的 shell payload,应当额外叠加一层 seccomp 白名单(例如只允许
read/write/openat/exit_group/exit/brk/mmap/munmap/close/fstat/lseek这 11 个 syscall)。 - 可见性差:nsjail 的 stdout/stderr 是直接管道到 host,不像 firecracker 那样有 virtio-serial 这种结构化通道。如果 Agent 需要「看到工具执行的精确 timing 和资源曲线」,往往要自己 wrap 一层。
- 不支持 Windows:在 cross-platform 桌面 Agent(Electron + 本地 LLM)场景下,要换成 Windows Job Objects + AppContainer。
一个生产可用的「Agent shell 沙箱」最小集大概是这样的:nsjail.cfg 声明 mount: ["tmpfs:/tmp"] + cgroup_mem_max: 512M + cgroup_pids_max: 64 + seccomp_policy: agent-strict.txt,host 端用 tokio/async 调度,每个 tool_call 起一个 nsjail 子进程,wall time 5s 强 kill,超时则记录「truncated reason」。这是今天大多数头部 Agent 产品(OpenAI 的 code interpreter 雏形、Anthropic 的 tool use 实验台)实际在用的方案。
四、第二层:浏览器自动化的隔离——browser-use 与 Playwright 的进程拓扑
浏览器自动化是 Agent 的第二类「重工具」:它需要 JS 执行、需要 DOM、需要 cookie 与 localStorage、需要 iframe 跨域访问,因此它的危险半径比 shell 大得多。一个被 prompt injection 控制的网页可以注入 iframe、发起跨域请求、读取 localStorage 里的 token、调用 WebUSB,甚至诱导用户授权 OAuth。
主流的浏览器 sandbox 模式有三种:
- 同进程隔离(最弱):Chromium 本身的 sandbox(macOS sandbox / Linux namespaces / Windows tokens)。它能阻止渲染进程访问文件系统,但同进程内的 prompt injection 仍能「读取用户已经登录的会话」。
- 多 profile 隔离(中等):每个 Agent 任务用独立的 Chromium profile,cookie 和 localStorage 完全隔离。这是今天 Playwright 在多租户评测系统里常见的做法。
- 远程浏览器隔离(RBI,最强):Agent 控制的浏览器跑在远端容器里(典型如 Browserless、Anchor Browser、Hyperbrowser),host 端只通过 CDP 协议收发指令,屏幕流通过 WebRTC/H.264 回传。这是 2026 年 enterprise Agent 部署的常见架构。
我们的工程经验是:第三层(RBI)必须成为 default,第二层(profile 隔离)作为 fallback,第一层(同进程)只能在用户显式选择「fast mode」时启用。原因很简单——第一层完全无法防御「网页内容通过 DevTools Protocol 把主进程键盘事件劫持」这种新型攻击,而第三层的成本其实远低于预期:8c16g 的节点可以稳定承载 50 个并发 Chromium 实例,每个实例的内存 footprint 经过优化可以压到 350MB 左右。
特别提一下 browser-use 这个 2025-2026 兴起的 Agent 浏览器框架。它的架构价值是把「浏览器的 DOM → Agent 的 observation」这一段做了规范化:每个 step 输出一个结构化 snapshot(accessibility tree + element refs),Agent 在这个 snapshot 上做决策,再把决策翻译回 CDP 命令。这避免了直接让 LLM 看截图(昂贵且不稳定),同时也自然引入了「Agent 看到的 web 和用户看到的 web」之间的解耦——这是 sandbox 思维在浏览器场景的具体落地。
五、第三层:长任务与代码解释器——firecracker 微内核 VM 的工程真相
当 Agent 的任务从「跑一个 Python 表达式」升级到「跑一段带 numpy、pandas、matplotlib 的 notebook」,再到「跑一段需要 GPU 的 ML 推理」,nsjail 和 gVisor 就开始吃力了。这时候我们不得不进入真正的 VM 级别隔离——firecracker。
firecracker 的核心定位是「微内核 hypervisor,启动 < 125ms,memory overhead < 5MB,每个 microVM 拥有独立内核」。它的设计取舍和普通 QEMU/KVM 完全相反:放弃 BIOS、放弃复杂设备模型、只保留 virtio-net / virtio-block / virtio-console / serial console。这让它特别适合 Agent 代码解释器场景:
- 极快冷启动:实测在 c6i.4xlarge 节点上,firecracker microVM 从 image 到 ready 平均 100ms 左右。
- 每 VM 一个独立内核:即使 microVM 内部出现了内核级 CVE,由于它是独立的 guest kernel,逃逸到 host 的代价极高(需要攻破 KVM + virtio)。
- 可序列化 rootfs:每个 microVM 的 rootfs 是 block device 上的一个只读 ext4 + 一个 read-write overlay,关机即丢弃,重启即干净。这天然实现了 sandbox 的「可归零」不变量。
但 firecracker 在 Agent 工程里有一个绕不过去的坎:host-agent 通信。microVM 没有外部 IP(除非显式配 TAP),它和 host 之间的通信只能走 virtio-serial 或 vsock。这要求我们必须设计一套「host API daemon」——一个常驻在 host 上的服务,负责把 microVM 内的 tool_call 请求转译成 host 上的 RPC、把 host 上的文件注入转译成 microVM 内的挂载、把 microVM 的 stdout 序列化后返回给 Agent runtime。这套 daemon 的设计质量直接决定了 firecracker 在 Agent 体系里的可用性,因为任何 host-agent 通信的瓶颈(比如 JSON 序列化慢、vsock buffer 太小)都会直接变成 Agent 端到端延迟的瓶颈。
host API daemon 的标准实现大概是这样的:daemon 进程监听 vsock port 1024(这是 firecracker 约定的 agent port),microVM 内的 user agent 进程通过 vsock 与 daemon 建立长连接;daemon 收到 microVM 的 tool_call 后,转发到 host 上的 tool registry 执行;执行结果通过同一个 vsock 连接回传给 microVM。这套架构里有两个工程陷阱需要警惕:第一,vsock buffer 大小默认只有 4KB,当 tool_call 的 payload 较大(比如一段 50KB 的代码)时,必须显式调大到 64KB-256KB,否则会出现 「silent truncation」——microVM 写入完整 payload 但 host 只收到前 4KB,然后整个 tool_call 在 host 端表现为 「请求格式错误」。第二,daemon 单点故障——如果 host API daemon 挂了,所有正在执行的 microVM 都会卡住等待 vsock 响应,这是 firecracker 集群最常见的雪崩触发点。生产里必须给 daemon 配 supervisor(systemd / supervisord),daemon 崩溃后 5 秒内自动重启;更进一步的做法是 daemon 双实例互备,vsock 连接在主备之间实现毫秒级切换。
e2b、Modal、Vercel Sandbox 这些产品本质上是把这一层封装好,提供给 Agent 工程师一个「firecracker as a service」的 API。它们各自的设计取舍差异很大:e2b 的核心优势是 API 极简(一个 Sandbox.create() 就够了),但代价是 microVM 模板固定,复杂定制能力弱;Modal 的优势是允许 Python 装饰器直接声明 sandbox 内的代码,融合度更高,代价是绑死在 Modal 的 runtime 上;Vercel Sandbox 则是面向 Next.js 应用场景的优化,对前端 Agent 友好但不擅长 ML 场景。2026 年的实际生产架构里,绝大多数头部 Agent 产品不再自建 firecracker 集群,而是订阅 e2b 或 Modal,把 firecracker 当作一个弹性 substrate。
一个被很多人忽视的细节是 GPU 透传。如果 Agent 需要跑 Stable Diffusion 或者本地 LLM 推理(Ollama / vLLM / llama.cpp),firecracker 默认不能透传 GPU,需要走 SR-IOV 或 NVIDIA CUDA-vDevice。这是 enterprise Agent 部署里最常见的「我装了 e2b 为什么跑不了 llama」的真因——答案是必须升级到支持 MIG 切片的 e2b dedicated tier,或者自建 firecracker + vfio。
六、第四层:统一视角——sandbox 作为 LLM 输出的「编译器后端」
把上面三层放到一起,我们能看到 sandbox 的本质是 LLM 输出的编译器后端。LLM 输出的是一段带语义的代码(tool_call JSON),sandbox 是这段代码的执行平台。它和 LLVM 的关系高度类似:
- LLM 输出相当于 LLVM IR,是中间表示,不是可直接执行的机器码。
- sandbox 相当于 target machine,决定了这段 IR 在什么硬件 / 内核 / 命名空间里执行。
- 不同的 sandbox 实现(nsjail / firecracker / wasm)相当于不同的 target architecture(x86 / ARM / RISC-V)。
这个视角带来的工程好处是显著的:我们可以把「sandbox 选择」从「DevOps 的某个开关」变成一个编译期决策。Agent 的 tool registry 在注册一个 tool 时,可以声明它的 sandbox tier:
@tool(sandbox_tier="microvm", max_wall_time=300, gpu="none")
def run_python_notebook(code: str) -> str:
...
@tool(sandbox_tier="nsjail", max_wall_time=5, gpu="none")
def run_shell(cmd: str) -> str:
...
@tool(sandbox_tier="rbi-browser", max_wall_time=120, gpu="none")
def browse_url(url: str) -> str:
...
当 Agent runtime 调度时,它会读取这个 tier 注解,把 tool_call 路由到对应的 sandbox backend。这种「声明式 sandbox」的好处是把安全策略和业务代码解耦,sandbox 的升级(从 nsjail 升级到 firecracker)不需要改一行业务代码,只需要改 tier 注册表。
更进一步,这种视角还给我们带来一个意想不到的好处:sandbox 成为可测试的、可回归的工程对象。我们可以写「sandbox unit test」——固定 (tool_call, payload, tier),断言 sandbox 输出的文件系统状态、网络 bytes、stdout 完全 deterministic。这正是今天 e2b / Modal 提供「snapshot replay」能力的理论基础,也是 2026 年 Agent 测试工程(id=477 那一篇的延伸)真正能在生产环境落地的关键。
七、对工程实践的推论
把 sandbox 视为「LLM 输出的编译器后端」之后,几个具体的工程推论变得自然:
推论一:tool registry 必须升级为「sandbox-aware」。 老的 tool registry 只有「name + schema + handler」三件套,新的至少要有五件套——加 sandbox_tier 和 audit_contract。前者决定执行平台,后者声明审计字段(执行前 / 执行后 / 拒绝原因三组)。这是 sandbox 真正能成为工程对象的前提。
推论二:默认 tier 必须是「中等强度」。 不要默认 nsjail(强度不够),也不要默认 firecracker(启动延迟太长)。我们的经验是 nsjail + strict seccomp 作为 default,firecracker 仅在 tool 显式声明 microvm tier 时启用。这样既覆盖了 90% 的普通 tool_call,又给「需要 GPU 的代码解释器」留了升级路径。
推论三:可观测性必须是 first-class。 sandbox 不是「跑完就完了」的黑盒,每一次执行都必须留下至少四类痕迹:(a) 输入 payload 的 hash(不是明文,避免污染审计);(b) sandbox tier + 启动耗时;(c) 资源使用曲线(CPU、内存、网络 bytes);(d) 拒绝 / 截断事件列表。这四类痕迹加起来,是事后做 incident review 时唯一能区分「sandbox 策略写错了」和「Agent 真的想这么干」的依据。
推论四:必须为 sandbox 故障设计降级路径。 firecracker 集群挂了怎么办?nsjail 策略写错了怎么办?每个 sandbox tier 都要有 fallback。常见做法是「firecracker 降级到 nsjail + 严格 timeout,nsjail 降级到 seccomp-only 单进程」三段式降级。降级路径的响应时间要纳入 SLO,因为 sandbox 挂掉的瞬间就是生产事故最容易爆发的瞬间。
推论五:建立「sandbox 压力测试」作为 CI 一环。 每个月定期跑一次「已知恶意 payload 集」(prompt injection 数据集 + CVE payload 集 + 内部红队 payload 集)穿过所有 sandbox tier,统计「逃逸尝试次数」「被拦截次数」「误杀业务请求次数」三个指标。这是 sandbox 工程的「金丝雀」,缺它一次重大事故就足以让整个 Agent 产品线停摆。
八、讨论:sandbox 的边界与未解决的问题
sandbox 不是万能药,至少有三个边界我们必须在工程上承认:
边界一:跨 sandbox 边界的攻击。 如果一个 Agent 同时在 nsjail 和 firecracker 里跑两个子任务,并通过 host 上的 message bus 通信,攻击面就从「单 sandbox 逃逸」变成了「跨 sandbox 编排攻击」。这种攻击的防御不在 sandbox 层,而在 message bus 层——必须对跨 sandbox 消息做签名 / 序列号 / schema 校验。
边界二:sandbox 启动延迟的「尾延迟」问题。 firecracker 启动 100ms 是平均值,但当 host 上同时启动 50 个 microVM 时,由于 KVM slot 竞争和 rootfs I/O 抖动,p99 可能飙升到 1.5s。这是 firecracker 在生产环境里最被低估的问题,必须在容量规划时把 p99 纳入,而非只看平均值。
边界三:审计成本与隐私的冲突。 sandbox 的「可解释」不变量要求把每次执行的 payload 都留痕,但 GDPR / CCPA / 中国《个人信息保护法》都要求不存储敏感数据。这两者之间存在天然张力,常见的工程折中是「payload 只存 hash,原始 payload 在工具执行后立即 tokenize 加密,仅 incident review 时解密」。但这套机制自身的密钥管理又是一个新的工程域。
最后还有一个未解决的开放问题:当 Agent 真的拥有了调用 sandbox 的能力,它对 sandbox 的策略本身是否应该有发言权? 也就是说,Agent 是否应该能提议「这个任务需要更宽松的 sandbox tier」?这是一个产品决策而非纯技术决策,今天的工程答案是「不允」,但这个答案可能在 2027 年随着 Agent 的可信度提升而被重新审视。
九、给 SRE 与平台工程师的可观测性清单
最后给负责维护 Agent sandbox 平台的 SRE 一份实操清单(每条都是「必须能在 5 分钟内查到」的硬指标):
- 每个 tier 的 p50/p95/p99 启动延迟——按小时维度画曲线,p99 > 1s 触发 oncall。
- 每个 tier 的「拒绝事件 / 总执行事件」比率——> 0.5% 意味着策略可能过严或被攻击。
- 每个 tier 的 resource leak 计数——microVM / cgroup / tmpfs / tap 设备的资源回收率必须 100%。
- 跨 sandbox 消息的签名失败计数——任何签名失败都意味着潜在攻击,必须实时告警。
- payload hash 与 incident 关联的命中率——事故 review 时,能否从「某次 sandbox 拒绝」快速反查到对应 payload,决定整个事故归因的速度。
把这五条做好,sandbox 平台就具备了「可观测、可告警、可归因」的基本面,再叠加前面四节讨论的「声明式 tier + 三段式降级 + 压力测试」,一个生产级 Agent sandbox 体系才算真正站住脚。
参考文献
- firecracker: lightweight virtualization for serverless applications, AWS white paper, 2020.
- gVisor: protecting google cloud users from container escapes, Google Cloud white paper, 2018.
- nsjail: A lightweight process isolation tool, Google open source, https://github.com/google/nsjail.
- Browser security model: Same-origin policy and cross-origin resource sharing, MDN Web Docs, 2024.
- Playwright: A framework for Web Testing and Automation, Microsoft, https://playwright.dev.
- browser-use: enabling natural language browser automation, https://github.com/browser-use/browser-use.
- e2b: secure sandboxed environments for AI agents, https://e2b.dev.
- Modal: serverless compute for data teams, https://modal.com.
- The Linux programming interface: a Linux and UNIX system programming handbook, Michael Kerrisk, No Starch Press, 2010.
- Capability Hardware Enhanced RISC Instructions: CHERI architecture for memory safety, University of Cambridge, 2019.
- NVIDIA Multi-Instance GPU (MIG) user guide, NVIDIA, 2023.
- WebAssembly specification: a portable binary instruction format, W3C, 2024.
- Chromium sandbox design document, Google Chromium Project, 2023.
- SR-IOV specification, PCI-SIG, 2024.
- The Prompt Injection attack surface in LLM-integrated applications, Greshake et al., arXiv:2303.04671, 2023.
一句话摘要:把 sandbox 视为 LLM 输出的编译器后端,按工具类型声明 tier(nsjail / firecracker / RBI),用 tool registry 把策略与业务解耦,再叠加 p99 启动延迟、拒绝率、资源回收、签名失败、payload 关联五条可观测性硬指标——这是 2026 年生产级 Agent sandbox 体系的最小完整闭环。