跳转至

附录 2A:Agent 沙箱

Agent 能调工具这件事,一旦工具是"执行任意代码"或"运行 shell 命令",安全问题就从理论变成了现实。模型输出一段 Python 代码,你的运行时去执行了——这段代码能读写文件系统、发网络请求、安装恶意包、甚至删除整个目录。

沙箱的目的只有一个:即使 Agent 执行了恶意或错误的代码,损害也被限制在一个隔离环境内,不波及宿主机和生产系统


为什么需要沙箱

不是所有工具都需要沙箱。get_weather 这种函数,参数是字符串、返回是字典,你自己写的代码,不需要隔离。

需要沙箱的场景是 Agent 执行不可预测的代码

  • 代码执行工具:Agent 生成 Python/JS 代码并运行
  • Shell 命令工具:Agent 生成 bash 命令并执行
  • 插件系统:第三方提供的工具代码,你没审计过
  • 文件操作:Agent 可以读写任意路径

没有沙箱时,一段看起来无害的代码就可能造成问题:

# Agent 生成的"分析数据"代码
import os
# 实际在遍历你的文件系统
for root, dirs, files in os.walk("/"):
    for f in files:
        if f.endswith(".env"):
            print(open(os.path.join(root, f)).read())

这段代码会找到并打印你机器上所有 .env 文件的内容——数据库连接串、API 密钥全暴露。


四种沙箱方案

Docker 容器

最常见的方案。把 Agent 的代码执行放在 Docker 容器里,容器和宿主机之间有文件系统隔离和网络隔离。

import docker

def run_in_docker(code: str, timeout: int = 30) -> dict:
    """在 Docker 容器中执行 Agent 生成的代码。"""
    client = docker.from_env()
    try:
        container = client.containers.run(
            image="python:3.12-slim",
            command=["python", "-c", code],
            mem_limit="256m",         # 内存限制
            cpu_period=100000,
            cpu_quota=50000,           # 50% CPU
            network_disabled=True,     # 禁用网络
            read_only=True,            # 只读文件系统
            remove=True,               # 执行完自动删除
            timeout=timeout,
        )
        return {"stdout": container.decode("utf-8"), "error": None}
    except docker.errors.ContainerError as e:
        return {"stdout": "", "error": str(e)}
    except Exception as e:
        return {"stdout": "", "error": f"容器异常: {e}"}

这段代码启动一个临时容器,在里面运行 Agent 生成的 Python 代码。network_disabled=True 阻止代码访问网络,read_only=True 阻止写入文件系统,mem_limitcpu_quota 限制资源消耗。

优点:生态成熟,大多数开发者熟悉,镜像可自定义。

缺点:容器启动需要几百毫秒到几秒,如果 Agent 频繁调用代码工具,延迟会累积。容器共享宿主机内核,隔离级别不如虚拟机——容器逃逸漏洞虽然少见但存在。

microVM(Firecracker)

AWS 做 Lambda 时遇到一个问题:Docker 容器的隔离对于多租户执行任意代码不够强。于是他们开源了 Firecracker——一个极轻量的虚拟机监控器(VMM),每个 microVM 有独立的内核。

宿主机
+-- microVM 1 (Agent A 的代码执行)
|   +-- 独立 Linux 内核 + 最小 rootfs
+-- microVM 2 (Agent B 的代码执行)
|   +-- 独立 Linux 内核 + 最小 rootfs
+-- ...

Firecracker microVM 的启动时间在 125ms 左右,内存开销约 5MB。比传统虚拟机轻很多,但比 Docker 容器的隔离更强——每个 VM 有自己的内核,容器逃逸类的攻击不适用。

实际使用时,一般不直接调 Firecracker API,而是通过封装好的平台:

  • AWS Lambda:底层就是 Firecracker
  • Fly.io Machines:每个 Machine 是一个 microVM
  • E2B:专门给 AI Agent 做的代码沙箱服务,底层是 Firecracker
# 使用 E2B 的 Code Interpreter(基于 Firecracker microVM)
from e2b_code_interpreter import Sandbox

def run_in_microvm(code: str) -> dict:
    """在 E2B microVM 沙箱中执行代码。"""
    sandbox = Sandbox()
    try:
        execution = sandbox.run_code(code)
        return {
            "stdout": execution.text,
            "error": execution.error.value if execution.error else None,
        }
    finally:
        sandbox.close()

优点:强隔离(内核级),启动快,AWS Lambda 已经大规模验证。

缺点:比 Docker 复杂,需要额外基础设施或第三方服务。本地开发不如 Docker 方便。

WebAssembly(Wasm)

WebAssembly 不是虚拟机,是一个指令集。代码编译成 Wasm 字节码后,在 Wasm 运行时(Wasmtime、Wasmer)里执行。Wasm 的安全模型是"默认什么都不能做"——没有文件系统访问、没有网络、没有系统调用,除非宿主显式授予。

Agent 代码 --> 编译为 .wasm --> Wasm 运行时执行
                                ↑
                           宿主决定授予哪些能力:
                           - 允许读 /tmp 目录?
                           - 允许 HTTP 请求?
                           - 允许多少内存?

优点:启动极快(微秒级),内存开销极小,安全模型干净。Cloudflare Workers 就跑在 Wasm 之上。

缺点:生态限制大。不是所有 Python 库都能编译成 Wasm(比如依赖 C 扩展的库)。如果 Agent 需要跑 pandasnumpy,Wasm 目前不是好选择。更适合执行简单、确定的计算逻辑。

gVisor

Google 的方案。gVisor 是一个用户态内核——它拦截容器内的所有系统调用,在用户空间模拟内核行为,不直接传给宿主机内核。

普通 Docker:     容器代码 --> Linux 内核(共享)
Docker+gVisor:   容器代码 --> gVisor(用户态内核) --> Linux 内核

Google Cloud Run 和 Google 内部很多服务用 gVisor。它比纯 Docker 更安全(系统调用被过滤),比 Firecracker 更轻(不需要独立内核镜像),但 syscall 兼容性不是 100%——某些程序可能跑不起来。


对比表

方案 隔离级别 启动时间 内存开销 生态兼容性 适合场景
Docker 容器 中(共享内核) 百毫秒至秒 开发测试、非多租户
Firecracker microVM 高(独立内核) 约125ms 约5MB 多租户生产、敏感代码
WebAssembly 高(无 syscall) 微秒级 极小 有限 简单计算、边缘计算
gVisor 中高(syscall 过滤) 和 Docker 接近 大部分兼容 Google Cloud、需要额外防护

生产实践建议

开发阶段:Docker 容器够用。设置好 network_disabledread_onlymem_limit,跑 Agent 生成的代码不会搞坏你的开发机。

上线初期:如果你的 Agent 面向用户执行代码(比如代码助手、数据分析工具),用 Firecracker 类服务(E2B、Fly Machines)或 Docker + gVisor。关键是多租户隔离——用户 A 的代码不能影响用户 B。

长期架构:把"执行不可信代码"这件事独立成一个服务。Agent 主进程和代码执行进程分开,通过 API 通信。这样即使沙箱方案以后要换(比如从 Docker 换到 Firecracker),Agent 核心逻辑不用改。

Agent 主进程                    代码执行服务
(工具调度、对话管理)   --API-->  (Docker/Firecracker/Wasm)
                                   |
                              隔离执行 Agent 生成的代码
                                   |
                       <--API--  返回 stdout/stderr

不管用哪种沙箱,有三条底线:

  1. 限制资源:内存、CPU、执行时间都要有上限。Agent 生成死循环是常事。
  2. 限制网络:除非有明确需求,默认禁用网络访问。防止代码外传数据。
  3. 限制文件系统:只读或只开放特定目录。Agent 不需要读 /etc/passwd