芥末
发布于 2026-02-07 / 0 阅读
0
0

Agent 沙箱技术选型:从本地运行时到 Firecracker MicroVM

Agent 系统越来越离不开两个基础能力:代码执行环境和可读写文件系统。

原因并不复杂。只把工具列表塞进大模型上下文里,通常不会让 Agent 更强,反而会带来两个问题:工具描述占用大量上下文,模型在过多工具中选择时也更容易出错。这类上下文被无关信息稀释、污染、挤占的现象,经常被称为 Context Rot。

更稳妥的做法是把一部分能力从上下文里挪出去。

例如,工具说明可以放在文件里,Agent 需要时再检索和阅读;中间结果也可以写入文件系统,而不是一直塞在上下文窗口中。这样一来,文件系统就不只是“临时目录”,而是 Agent 的外部记忆。代码执行环境也不只是“跑几行 Python”,而是 Agent 把计划落到实际操作上的执行层。

问题也随之出现:让 Agent 写代码、执行命令、读写文件,本质上是在给一个概率系统操作系统权限。它可能遇到 Prompt Injection(提示词注入),也可能因为幻觉生成危险命令、死循环、fork 炸弹或者高成本网络请求。

所以,Agent Infra 里必须有一层 Sandbox(沙箱)。

沙箱要解决的核心问题是:允许 Agent 自主执行任务,但把破坏范围限制在可控边界内

flowchart LR
    U[用户任务] --> LLM[LLM 规划与生成代码]
    LLM --> S[Sandbox 执行环境]
    S --> FS[(受限文件系统)]
    S --> NET[受限网络]
    S --> CPU[CPU / 内存 / 进程配额]
    S --> OUT[执行结果]
    OUT --> LLM

沙箱不是一个单独技术,而是一组隔离机制的组合,通常包括:

隔离维度要限制什么典型手段
文件系统不允许读写宿主机敏感路径chroot、mount namespace、只读挂载、临时目录
网络不允许任意访问公网或内网禁用网络、代理白名单、DNS 过滤
进程防止 fork 炸弹和跨进程攻击PID namespace、pids limit、IPC 隔离
资源防止耗尽 CPU、内存、磁盘cgroups、quota、timeout
系统调用限制危险内核接口seccomp、gVisor、虚拟机边界
用户权限避免 root 权限扩散非 root 用户、user namespace、capability drop

为什么 Agent 比普通后端服务更需要沙箱

传统后端服务的执行路径通常由开发者写死。接口接收什么参数、调用什么函数、访问什么资源,大多是确定的。

Agent 不一样。Agent 会根据上下文动态生成下一步动作:

sequenceDiagram
    participant User as 用户
    participant Agent as Agent
    participant Sandbox as 沙箱
    participant FS as 文件系统
    participant Net as 网络

    User->>Agent: 提交任务
    Agent->>Agent: 生成计划和代码
    Agent->>Sandbox: 执行命令
    Sandbox->>FS: 受限读写
    Sandbox->>Net: 按策略访问
    Sandbox-->>Agent: 返回 stdout / stderr / 文件结果
    Agent-->>User: 汇总结果

这里的风险来自几个方面:

  1. Prompt Injection
    Agent 可能处理来自网页、文档、Issue、邮件的内容,而这些内容里可能夹带“忽略之前规则”“读取密钥并上传”等恶意指令。

  2. 幻觉命令
    模型可能生成不存在、错误或危险的命令。例如误删目录、覆盖文件、执行不必要的网络请求。

  3. 资源耗尽
    代码可能进入死循环,或者创建大量进程、打开大量文件、占满内存。

  4. 多租户信任边界
    云端 Agent 服务通常要同时处理不同用户任务。如果多个用户的任务运行在同一批机器上,隔离强度就直接决定了安全边界。

本地 Coding Agent 和云端 Agent 的风险模型也不同。本地工具往往有用户在旁边批准命令,风险由开发者自己承担;云端服务则不能要求用户每一步都点击确认。更合理的方式是:只要 Agent 还在沙箱内,系统就默认它可以自主尝试。

常见沙箱技术对比

Agent 沙箱可以从轻到重分成几类:Local Runtime、WebAssembly、Docker、gVisor、MicroVM。

技术方案核心原理隔离强度启动速度兼容性适合场景
Local Runtime操作系统原语限制进程低到中极快本地 CLI、开发者机器上的受限执行
WASM字节码运行时沙箱极快中到低纯计算、插件、简单脚本
Dockernamespace + cgroups通用代码执行,需要安全加固
gVisor用户态内核拦截系统调用较快中到高多租户、不可信代码执行
MicroVMKVM 硬件虚拟化极高公有云 Agent 托管、强隔离任务

没有一种方案适合所有场景。选型时要同时考虑隔离强度、启动延迟、生态兼容性、运维复杂度和成本。

Local Sandbox Runtime:本地进程的受限执行

Local Runtime 的思路是:不启动完整容器或虚拟机,而是直接使用操作系统提供的隔离能力限制某个进程。

在 Linux 上,常用机制包括:

  • namespaces:隔离进程、网络、挂载点、用户等视图;
  • cgroups:限制 CPU、内存、进程数等资源;
  • seccomp:限制系统调用;
  • bind mount / read-only mount:控制文件系统读写范围。

在 macOS 上,也可以看到 sandbox-exec 这类机制,不过它已经不太适合作为长期依赖的核心安全边界。

Local Runtime 的典型形态是“受限 Bash 运行环境”。Agent 生成命令后,不是直接交给宿主机 shell,而是交给一个带策略的 runtime 执行。

示例配置可以长这样:

{
  "sandbox": {
    "enabled": true,
    "autoAllowBashIfSandboxed": true,
    "excludedCommands": ["git", "docker"],
    "network": {
      "allowUnixSockets": ["/var/run/docker.sock"],
      "allowLocalBinding": true
    }
  }
}

这类配置通常会约束两类能力:

能力限制方式
文件系统只允许读写指定目录,禁止访问敏感路径
网络通过本地代理、Unix Socket、白名单限制访问范围

Anthropic 开源过 sandbox-runtime,可以作为独立命令使用:

npx @anthropic-ai/sandbox-runtime <command-to-sandbox>

也可以把它挂到自己的 Agent 工程中,让 Agent 的命令执行都经过同一层策略控制。

Local Runtime 的优势是轻量,不需要拉镜像,也不需要启动虚拟机,非常适合本地开发工具。但它依赖宿主机内核和本机安全配置,一旦内核或策略配置存在漏洞,隔离边界就可能被绕过。

适合场景:

适合不适合
本地 Coding Agent公有云多租户不可信代码
CLI 工具的安全执行强隔离合规场景
对启动速度极敏感的轻任务需要完整 Linux 环境的复杂任务

WebAssembly:用字节码边界隔离代码

WebAssembly(WASM,网页汇编)最初为浏览器里的高性能代码执行设计,天然带有沙箱属性。代码被编译成 .wasm 二进制格式后,由运行时加载、校验和执行。

WASM 的关键安全模型是:代码不能随便访问进程内存,只能访问运行时分配给它的线性内存区域;如果越界访问,会被运行时拦截。

flowchart LR
    Code[源代码] --> Compile[编译为 wasm]
    Compile --> Runtime[WASM Runtime]
    Runtime --> Memory[受控线性内存]
    Runtime --> Wasi[WASI 能力接口]
    Wasi --> Host[宿主资源]

WASI(WebAssembly System Interface,WebAssembly 系统接口)给 WASM 程序提供受控的文件、时间、随机数等能力。是否允许访问某个目录、是否允许网络访问,都可以由宿主运行时控制。

WASM 的优点很明显:

  • 冷启动非常快;
  • 资源占用低;
  • 沙箱边界清晰;
  • 适合运行小型插件或纯计算逻辑。

但它在 Agent 场景里有一个明显短板:生态兼容性。

例如 Python 可以通过 Pyodide 等方式运行在 WASM 里,但很多依赖 C 扩展、系统库或复杂运行时能力的库会遇到兼容问题。数据分析里常见的 NumPy、Pandas、SciPy 某些能力也可能受限。网络栈、文件系统语义、子进程模型和标准 Linux 环境不完全一致,这会让通用 Code Interpreter 变得麻烦。

WASM 更适合执行边界明确的小任务,而不是运行任意用户生成的 Python 项目。

Docker:通用但必须加固

Docker 是很多 Agent 沙箱的第一选择,因为它兼容性好、生态成熟、镜像构建方便。大多数 Python、Node.js、Shell、数据分析工具都能直接跑在容器里。

但标准 Docker 不能直接等同于安全沙箱。

容器本质上仍然是宿主机上的进程。它通过 Linux namespace 隔离视图,通过 cgroups 限制资源,但容器内进程最终还是会向同一个宿主机内核发起系统调用。

flowchart LR
    A[Agent 代码] --> C[Docker 容器进程]
    C --> S[系统调用]
    S --> K[宿主机 Linux 内核]
    K --> H[宿主机资源]

如果容器内代码触发内核漏洞,或者容器配置开放了过多权限,就可能发生容器逃逸。因此,用 Docker 跑不可信 Agent 代码时,必须进行安全加固。

一个更安全的 Docker 启动示例:

docker run \
  --cap-drop ALL \
  --security-opt no-new-privileges \
  --security-opt seccomp=/path/to/seccomp-profile.json \
  --read-only \
  --tmpfs /tmp:rw,noexec,nosuid,size=100m \
  --tmpfs /home/agent:rw,noexec,nosuid,size=500m \
  --network none \
  --memory 2g \
  --cpus 2 \
  --pids-limit 100 \
  --user 1000:1000 \
  -v /path/to/code:/workspace:ro \
  -v /var/run/proxy.sock:/var/run/proxy.sock:ro \
  agent-image

关键参数的作用如下:

参数作用
--cap-drop ALL移除 Linux capabilities,避免容器获得额外内核权限
--security-opt no-new-privileges禁止进程通过 setuid 等方式获得新权限
--security-opt seccomp=...限制系统调用,减少内核攻击面
--read-only根文件系统只读,防止持久化篡改
--tmpfs /tmp:...提供临时可写目录,容器销毁后数据消失
--network none禁用默认网络,避免任意访问外部或内网
--memory 2g限制内存,防止资源耗尽
--cpus 2限制 CPU 使用
--pids-limit 100限制进程数量,防止 fork 炸弹
--user 1000:1000使用非 root 用户运行
-v /path/to/code:/workspace:ro只读挂载代码,允许分析但不允许修改
-v proxy.sock:...:ro通过受控代理访问外部能力

还可以在 Docker daemon 层启用 userns-remap,把容器内 root 映射到宿主机上的非特权用户。这样即使容器里出现 root 权限,也不会直接等于宿主机 root。

需要特别注意的是,不要把敏感目录挂进容器,例如:

~/.ssh
~/.aws
~/.config
/var/run/docker.sock
/etc

尤其是 /var/run/docker.sock。如果容器能访问宿主机 Docker Socket,它基本就拥有了控制宿主机容器的能力,这会显著削弱沙箱意义。

Docker 的优点是通用,缺点是隔离边界不够硬。对于内部可信任务、训练数据生成、低风险代码执行,它是成本很低的选择;对于多租户不可信代码,最好叠加 gVisor、Kata Containers 或 MicroVM。

gVisor:给容器加一层用户态内核

gVisor 的目标是解决标准容器直接共享宿主机内核的问题。

它在容器和真实内核之间放了一个用户态内核组件,叫 Sentry。容器内进程发起系统调用时,不是直接打到宿主机内核,而是先被 Sentry 拦截、模拟和过滤。只有必要的底层调用才会继续传给真实内核。

flowchart TB
    subgraph Docker["标准 Docker"]
        A1[Agent 代码] --> S1[Syscall]
        S1 --> K1[宿主机真实内核]
    end

    subgraph Gvisor["gVisor"]
        A2[Agent 代码] --> S2[Syscall]
        S2 --> G[Sentry 用户态内核]
        G --> K2[宿主机真实内核]
    end

这样做的结果是,恶意代码不能直接接触真实内核的大量接口。攻击者需要先绕过 gVisor 的用户态实现,再找到能影响宿主机内核的路径,攻击面比标准 Docker 小得多。

Docker 使用 gVisor 时,需要安装 runsc runtime,并在 Docker daemon 中注册:

{
  "runtimes": {
    "runsc": {
      "path": "/usr/local/bin/runsc"
    }
  }
}

运行容器时指定 runtime:

docker run --runtime=runsc agent-image

gVisor 的代价主要在系统调用上。不同任务的开销差异很大:

工作负载典型开销
CPU 密集型计算接近无额外开销
简单系统调用可能变慢约 2 倍
大量小文件 I/O在极端模式下可能慢很多
网络请求密集型取决于调用频率和网络路径

如果任务主要是矩阵计算、模型推理前后处理、批量数据运算,gVisor 的额外成本通常不明显。若任务频繁打开关闭小文件、发起大量网络连接、执行高频系统调用,延迟可能会明显增加。

gVisor 适合以下场景:

适合原因
多租户代码执行比标准容器更强的内核隔离
不可信用户脚本系统调用被用户态内核拦截
云端 Agent Serving隔离强度和启动速度比较均衡

MicroVM:用轻量虚拟机建立硬边界

MicroVM 的核心思路是:保留虚拟机的硬隔离,但砍掉传统虚拟机里不适合高密度任务的复杂设备和启动流程。

Firecracker 是 MicroVM 的代表实现。它基于 KVM(Kernel-based Virtual Machine,Linux 内核虚拟机)提供硬件虚拟化隔离,专门面向 Serverless 和多租户容器场景。

传统虚拟机通常包含大量通用设备模拟能力,例如显卡、USB、BIOS、复杂外设等。Agent 沙箱并不需要这些。Firecracker 只保留最核心的能力:

  • vCPU;
  • 内存;
  • 网络设备;
  • 块设备;
  • 最小化设备模型。
flowchart TB
    Host[宿主机]
    KVM[KVM 硬件虚拟化]
    VM1[MicroVM A]
    VM2[MicroVM B]
    VM3[MicroVM C]

    Host --> KVM
    KVM --> VM1
    KVM --> VM2
    KVM --> VM3

    VM1 --> A1[Agent 任务 A]
    VM2 --> A2[Agent 任务 B]
    VM3 --> A3[Agent 任务 C]

MicroVM 的优势是隔离强度非常高。每个 Agent 任务运行在独立虚拟机里,即使任务内部获得了 root,也只是虚拟机里的 root,不等于宿主机 root。

Firecracker 的冷启动可以做到很低,内存额外开销也比传统虚拟机小得多,因此被很多云端沙箱和 Serverless 平台采用。E2B、Modal 等 Agent / 代码执行基础设施,经常会在底层使用 Firecracker 或类似轻量虚拟化能力。

除了 Firecracker,还有两类相关方案:

方案特点
QEMU 传统虚拟机功能完整、兼容性强,但启动慢、资源占用高
Kata Containers对外像容器,对内用轻量虚拟机包裹容器,可结合 QEMU 或 Firecracker

MicroVM 的主要问题是运维复杂度。网络配置、镜像构建、快照恢复、资源调度、磁盘生命周期、日志采集都比 Docker 更复杂。如果团队没有足够基础设施能力,直接使用托管沙箱服务通常更现实。

按工程场景选型

沙箱选型不能只看“谁最安全”。安全只是一个维度,真实系统还要考虑延迟、吞吐、成本、兼容性和运维能力。

自建还是托管

自建适合对数据边界、网络边界、合规要求非常严格的团队。可以基于 Hardened Docker、gVisor、Kata Containers 或 Firecracker 搭建内部执行集群。

托管服务则适合快速验证 Agent 产品,尤其是团队不想自己处理 Firecracker 网络、快照、调度和隔离细节时。

方式优点代价
自建数据边界可控,可深度定制网络和权限运维复杂,需要安全经验
托管接入快,省去底层调度和隔离实现成本较高,数据和执行环境依赖供应商

常见托管类沙箱包括 E2B、Daytona、Runloop、Vercel Sandbox、Cloudflare Sandbox 等。选择时重点看几个指标:启动延迟、可用镜像、持久化能力、网络控制、并发上限、日志审计和数据驻留策略。

短生命周期还是长生命周期

很多 Code Interpreter 场景适合“一次性沙箱”:请求来了启动环境,执行完销毁。这样数据残留少,安全模型也简单。

但 Agent Skills、长期任务、文件记忆等场景需要持久化文件系统。Agent 可能需要在多轮会话之间保存脚本、缓存、结果文件和工具说明。

生命周期可以抽象成这样:

stateDiagram-v2
    [*] --> Running: create
    Running --> Paused: pause
    Paused --> Running: resume / connect
    Running --> Killed: kill
    Paused --> Killed: kill
    Killed --> [*]

E2B 这类服务通常支持暂停、恢复和销毁沙箱:

from e2b_code_interpreter import Sandbox

sandbox = Sandbox.create()  # Running

sandbox.betaPause()         # Running -> Paused

sandbox.connect()           # Paused -> Running

sandbox.kill()              # Running/Paused -> Killed

短生命周期和长生命周期的取舍如下:

生命周期适合场景风险与代价
用完即毁普通代码执行、数据分析、一次性任务冷启动和环境准备成本更高
可暂停恢复Agent Skills、多轮任务、文件记忆需要处理数据隔离、过期清理、配额管理
长期常驻复杂工作空间、持续后台任务成本高,安全审计和资源回收更复杂

面向用户服务还是训练 Rollout

Agent 沙箱有两种很不一样的负载。

一种是 User-facing Serving,也就是直接面对用户请求。它关注响应延迟、租户隔离、可用性和错误恢复。这类场景适合 gVisor 或 Firecracker,并配合 Warm Pool 预热机制减少冷启动。

另一种是 Training & Data Generation。例如在强化学习或 RLVR(Reinforcement Learning with Verifiable Rewards,可验证奖励强化学习)中,需要让 Agent 大规模并行执行代码,生成可验证结果和奖励数据。此时更关注吞吐和成本,任务通常在内部集群中运行,隔离要求可能低于公有云多租户服务。加固后的 Docker 往往已经能满足需求。

场景核心指标推荐方案
用户在线服务延迟、隔离、稳定性gVisor、Firecracker、托管沙箱
多租户代码平台强隔离、审计、资源控制Firecracker、Kata、gVisor
训练 Rollout吞吐、成本、批量调度Hardened Docker、Kubernetes Jobs
内部可信任务兼容性、开发效率Docker、Local Runtime

Text-only 还是 GUI / Browser

Code Interpreter 通常只需要标准输入输出和文件系统。它运行 Python、Shell、Node.js,返回 stdout、stderr、图表文件或数据文件即可。这类任务资源需求较低。

Browser Agent 或 Computer Use 则不同。它需要浏览器、图形界面、截图、鼠标键盘事件,有时还要通过 VNC 或 WebRTC 把实时画面传给 VLM(Vision-Language Model,视觉语言模型)。

flowchart LR
    Agent[Agent] --> Browser[浏览器沙箱]
    Browser --> Screenshot[截图流]
    Screenshot --> VLM[VLM 识别页面]
    VLM --> Agent
    Agent --> Action[点击 / 输入 / 滚动]
    Action --> Browser

两类沙箱的差异很大:

类型需要的能力资源消耗典型方案
Text-onlyREPL、文件读写、命令执行Docker、gVisor、MicroVM
Browser / GUI浏览器、截图、远程控制、网络策略Browserbase、steel-browser、browser-use-sandbox

GUI 沙箱不仅要隔离代码,还要隔离浏览器状态、Cookie、下载文件、剪贴板和网络访问。它的成本模型更接近“远程桌面实例”,不能按普通 REPL 沙箱估算。

主流沙箱方案梳理

可以把现有方案按用途分成三类。

类型代表方案适合解决的问题
通用托管沙箱E2B、Daytona、Runloop、Vercel Sandbox、Cloudflare Sandbox快速接入代码执行、持久化工作区、托管隔离环境
轻量开源沙箱BoxLite、Microsandbox、anthropic-experimental/sandbox-runtime、agent-infra/sandbox本地或自建环境里的受限执行
浏览器沙箱Browserbase、steel-browser、browser-use-sandboxBrowser Agent、Computer Use、网页自动化

选择供应商或开源项目时,不要只看 API 是否简单,还要检查这些能力:

检查项为什么重要
是否支持网络白名单防止 Agent 扫描内网或访问敏感服务
是否支持资源配额防止死循环、内存爆炸和 fork 炸弹
是否支持文件持久化决定能否承载 Agent Skills 和工作区
是否支持镜像定制决定能否预装依赖,减少冷启动
是否有审计日志方便排查风险命令和异常行为
是否明确隔离底层Docker、gVisor、Firecracker 的安全边界不同
是否支持超时和强制销毁避免失控任务长期占用资源

一个实用选型路径

可以用下面的路径快速判断该选哪类沙箱:

flowchart TD
    A[Agent 是否执行不可信代码] -->|否| B[Local Runtime 或 Docker]
    A -->|是| C{是否多租户}
    C -->|否,内部任务| D[Hardened Docker 或 gVisor]
    C -->|是| E{隔离要求是否极高}
    E -->|中高| F[gVisor / Kata Containers]
    E -->|极高| G[Firecracker MicroVM]
    G --> H{团队是否能维护虚拟化集群}
    H -->|能| I[自建 MicroVM 平台]
    H -->|不能| J[托管 Sandbox 服务]

更简洁的判断方式:

需求优先选择
本地 CLI Agent,用户自己承担风险Local Runtime
小型纯计算插件WASM
内部批处理和训练数据生成Hardened Docker
云端不可信脚本执行gVisor
公有云多租户强隔离Firecracker / Kata
快速上线 Agent Code InterpreterE2B 等托管沙箱
浏览器自动化和 Computer UseBrowserbase / steel-browser

落地时最容易踩的坑

只限制文件,不限制网络

很多沙箱会认真做文件系统隔离,却忘了网络。Agent 一旦能任意访问网络,就可能:

  • 扫描云内网;
  • 访问 metadata service;
  • 上传敏感文件;
  • 调用高成本外部 API;
  • 被网页内容诱导访问恶意地址。

更稳妥的方式是默认禁用网络,再通过代理开放白名单。

docker run \
  --network none \
  -v /var/run/proxy.sock:/var/run/proxy.sock:ro \
  agent-image

由代理统一做域名、IP、协议和请求频率限制。

把宿主机敏感目录挂进沙箱

只读挂载也不是绝对安全。Agent 不一定要修改文件,读取密钥本身就已经造成泄露。

高风险挂载包括:

~/.ssh
~/.aws
~/.kube
~/.docker
~/.config
/var/run/docker.sock

尤其是云环境里的凭据文件、Kubernetes 配置、Docker Socket,都不应该进入不可信沙箱。

只做权限隔离,不做资源隔离

权限隔离防止越权,资源隔离防止拖垮系统。二者缺一不可。

最低限度应该配置:

  • CPU 限制;
  • 内存限制;
  • 进程数限制;
  • 文件大小限制;
  • 执行超时;
  • 空闲超时;
  • 最大输出大小。

否则一段简单代码就能让机器不可用:

# 内存耗尽示例:不要在真实环境执行
x = []
while True:
    x.append("x" * 1024 * 1024)
# fork 炸弹示例:不要在真实环境执行
:(){ :|:& };:

没有销毁和清理策略

长生命周期沙箱很方便,但也更容易积累风险。需要明确:

对象清理策略
临时文件任务结束删除
持久化工作区设置过期时间和容量上限
网络连接超时关闭
后台进程沙箱暂停或销毁时强制结束
日志脱敏后保存,设置保留周期

结论

Agent 的能力边界正在从“调用几个工具”变成“拥有一个可执行的工作空间”。代码执行环境负责把模型生成的计划变成真实操作,文件系统负责保存中间状态、工具说明和长期记忆。

这也意味着沙箱会成为 Agent Infra 的基础层,而不是可选增强项。

轻量任务可以从 Local Runtime 或 Hardened Docker 开始;多租户不可信代码适合 gVisor;强隔离云端执行更适合 Firecracker MicroVM;需要浏览器操作时,则要使用专门的 GUI / Browser 沙箱。

一个好的 Agent 沙箱,不是完全禁止 Agent 行动,而是让它在清晰边界内自主行动:能写文件,但只能写工作区;能访问网络,但只能走受控代理;能执行代码,但不能耗尽整台机器。这样才能在自动化能力和系统安全之间取得可落地的平衡。


评论