第 16 章

沙箱(Sandboxes)

在隔离环境中安全执行代码——让 Agent 拥有 Shell 能力而不危及宿主系统

从"只读文件"到"安全执行任意命令"的关键一步

为什么需要它

你的 Agent 需要运行 Shell 命令——但你能信任它吗?

没有沙箱的风险

  • Agent 运行 rm -rf /?你的文件全没了
  • Agent 读取 .env?密钥直接泄露
  • Agent 访问内网?整个网络暴露
  • Agent 安装恶意包?系统被植入后门
  • 多个 Agent 同时跑?互相干扰、资源争抢

沙箱的解法

  • Agent 在隔离容器中运行,碰不到宿主机
  • 文件系统完全隔离,无法读取你的密钥
  • 网络可配置,按需阻断外联
  • 每次运行是全新环境,无残留风险
  • 按执行时间付费,用完即销毁
问题核心:当你的 Agent 需要执行代码、安装依赖、运行测试——它就需要 Shell 权限。但你不应该把宿主机的 Shell 权限交给一个 LLM。沙箱就是那道安全边界。

沙箱是什么

沙箱是 Deep Agents 的一种后端——在隔离环境中提供文件系统 + Shell 执行

在 Deep Agents 中,沙箱就是后端(backends)。与其他后端(State、Filesystem、Store)不同,沙箱后端多了一个 execute 工具,让 Agent 可以在沙箱内运行任意 Shell 命令。

能力说明对比其他后端
文件系统工具 ls、read_file、write_file、edit_file、glob、grep 所有后端都有
execute 工具 运行任意 Shell 命令,返回 stdout/stderr 沙箱独有
安全边界 隔离容器保护宿主机系统 沙箱独有
文件传输 uploadFiles() / downloadFiles() 沙箱后端支持
架构示意(Agent ↔ 沙箱通信协议):Agent(LLM ↔ Tools)通过 backend protocol 与沙箱通信;沙箱内部提供文件系统(Filesystem)、Shell(Bash)和依赖管理(Dependencies)服务。这一协议是沙箱后端与 Agent 之间的标准通信接口。
import { createDeepAgent } from "deepagents";
import { DenoSandbox } from "@langchain/deno";
import { ChatAnthropic } from "@langchain/anthropic";

// 创建并初始化沙箱容器
const sandbox = await DenoSandbox.create({
  memoryMb: 1024,     // 分配 1GB 内存
  lifetime: "10m",    // 容器最大存活时间 10 分钟
});

try {
  const agent = createDeepAgent({
    model: new ChatAnthropic({ model: "claude-opus-4-6" }),
    systemPrompt: "You are a JavaScript coding assistant.",
    backend: sandbox,  // 沙箱作为后端,Agent 可使用 execute + 文件系统工具
  });

  const result = await agent.invoke({
    messages: [{ role: "user", content: "Create a HTTP server and test it" }],
  });
} finally {
  await sandbox.close();  // 必须关闭!否则持续计费
}
常见误区:沙箱后端会持续消耗资源并产生费用。必须在不再需要时调用 sandbox.close(),否则会一直计费。推荐使用 try/finally 确保清理。
图:Agent 与沙箱通信协议架构
使用 Deep Agents Code?Deep Agents Code 通过 --sandbox 标志内置了沙箱支持。有关具体设置、标志(--sandbox-id、--sandbox-setup)和示例,请参阅使用远程沙箱。

可用提供商

五种沙箱提供商,覆盖从本地开发到云端的完整场景

Modal

云端无服务器沙箱,按执行时间付费。

  • 支持 blockNetwork: true 阻断网络
  • 适合生产环境的弹性扩展
  • 自动管理容器生命周期

Daytona

完整的开发环境沙箱,支持原生 Git 操作。

  • 支持快照和标签管理
  • 内置 TTL 自动清理闲置环境
  • 适合需要 Git 工作流的编码 Agent

Deno

基于 Deno 运行时的轻量沙箱。

  • 启动快,适合快速原型
  • 原生 TypeScript/JavaScript 支持
  • 内置安全权限模型

Node VFS

基于 Node.js 虚拟文件系统的本地沙箱。

  • 无需外部服务,纯本地运行
  • 适合本地开发和测试
  • 不提供真实隔离,仅模拟文件系统

LangSmith

LangSmith 平台内置的托管沙箱。

  • 与 LangSmith 生态深度集成
  • 一体化追踪和调试
  • 适合已使用 LangSmith 的团队
版本要求:沙箱功能需要 deepagents>=1.7.0。
选择建议:本地开发用 Node VFS 或 Deno;需要真实隔离用 Modal 或 Daytona;已用 LangSmith 平台就用其内置沙箱。找不到合适的?可以实现自己的沙箱后端。
扩展阅读:没有找到你的提供商?你可以实现自己的沙箱后端。请参阅贡献沙箱集成了解如何为 LangChain 添加新的沙箱提供商支持。

典型场景

沙箱特别适合这两类 Agent——它们都需要执行代码但必须安全隔离

编码 Agent

自主运行的开发助手,需要完整的开发工具链。

  • 使用 Shell 执行构建和测试命令
  • 使用 Git 克隆仓库、提交代码(许多提供商提供原生 Git API,例如 Daytona 的 Git 操作)
  • 运行 Docker-in-Docker 构建镜像
  • 安装依赖并运行流水线
// 编码 Agent 典型工作流
// 1. execute: git clone 
// 2. read_file: 查看项目结构
// 3. edit_file: 修改代码
// 4. execute: npm install && npm test
// 5. execute: git commit && git push

数据分析 Agent

在隔离环境中处理数据、运行分析、生成报告。

  • 安装 pandas、numpy 等分析库
  • 加载和处理数据文件
  • 运行统计计算和可视化
  • 生成 PowerPoint、PDF 等输出
// 数据分析 Agent 典型工作流
// 1. uploadFiles: 上传 CSV 数据
// 2. execute: pip install pandas matplotlib
// 3. write_file: 分析脚本
// 4. execute: python analyze.py
// 5. downloadFiles: 下载分析报告

生命周期

两种作用域——每个对话一个沙箱,或所有对话共享一个沙箱

深度阅读:有关完整的生命周期表、异步 graph factory 说明、TTL 行为、LangGraph 部署接入以及客户端示例,请参阅《投入生产》中的沙箱生命周期。

线程作用域(默认)

每个对话获得自己的沙箱。首次运行时创建,同一线程后续交互复用。

  • 线程结束或 TTL 过期时环境被清理
  • 不同对话之间完全隔离
  • 推荐配置 TTL,空闲时自动清理
  • 适合需要隔离的多租户场景

助手作用域

同一助手上的所有线程共享一个沙箱。文件和包在不同对话间持久化。

  • 已安装的包、克隆的仓库跨对话复用
  • 减少重复安装,节省启动时间
  • 需要注意状态积累问题
  • 适合连续开发场景

线程作用域代码示例

# Python 线程作用域示例(Daytona)
from daytona import CreateSandboxFromSnapshotParams, Daytona
from deepagents import create_deep_agent
from langchain_core.runnables import RunnableConfig
from langchain_daytona import DaytonaSandbox

client = Daytona()

async def agent(config: RunnableConfig):
    thread_id = config["configurable"]["thread_id"]  # 用线程 ID 作为沙箱查找键
    try:
        # 尝试复用已有沙箱(同一线程后续对话)
        sandbox = await client.find_one(labels={"thread_id": thread_id})
    except Exception:
        # 首次运行,创建新沙箱
        sandbox = await client.create(
            CreateSandboxFromSnapshotParams(
                labels={"thread_id": thread_id},  # 打上线程 ID 标签
                auto_delete_interval=3600,  # TTL: 空闲 1 小时后自动清理
            )
        )
    return create_deep_agent(
        model="google-genai:gemini-3.1-pro-preview",
        backend=DaytonaSandbox(sandbox=sandbox)  # 沙箱作为后端
    )
// TypeScript 线程作用域示例(Daytona)
import { Daytona } from "@daytonaio/sdk";
import { DaytonaSandbox } from "@langchain/daytona";
import { createDeepAgent } from "deepagents";

const client = new Daytona();

export async function agent(config) {
  const threadId = config.configurable?.thread_id as string;
  let sandbox;
  try {
    // 尝试复用已有沙箱(同一线程后续对话)
    sandbox = await client.findOne({ labels: { thread_id: threadId } });
  } catch {
    // 首次运行,创建新沙箱
    sandbox = await client.create({
      labels: { thread_id: threadId },  // 打上线程 ID 标签
      autoDeleteInterval: 3600, // TTL: 空闲 1 小时后自动清理
    });
  }
  return createDeepAgent({
    model: "google-genai:gemini-3.1-pro-preview",
    backend: await DaytonaSandbox.connect(sandbox.id),  // 用沙箱 ID 构建后端
  });
}

助手作用域代码示例

同一助手上的所有线程复用同一个沙箱,文件和已安装包跨对话持久化。使用 assistant_id 作为标签键而非 thread_id。

# Python 助手作用域示例(Daytona)
from daytona import CreateSandboxFromSnapshotParams, Daytona
from deepagents import create_deep_agent
from langchain_core.runnables import RunnableConfig
from langchain_daytona import DaytonaSandbox

client = Daytona()

async def agent(config: RunnableConfig):
    assistant_id = config["configurable"]["assistant_id"]  # 按 assistant_id 查找沙箱
    try:
        sandbox = await client.find_one(labels={"assistant_id": assistant_id})
    except Exception:
        sandbox = await client.create(
            CreateSandboxFromSnapshotParams(labels={"assistant_id": assistant_id})
        )
    return create_deep_agent(
        model="google-genai:gemini-3.1-pro-preview",
        backend=DaytonaSandbox(sandbox=sandbox)
    )
// TypeScript 助手作用域示例(Daytona)
import { Daytona } from "@daytonaio/sdk";
import { DaytonaSandbox } from "@langchain/daytona";
import { createDeepAgent } from "deepagents";

const client = new Daytona();

export async function agent(config) {
  const assistantId = config.configurable?.assistant_id as string;
  let sandbox;
  try {
    sandbox = await client.findOne({ labels: { assistant_id: assistantId } });
  } catch {
    sandbox = await client.create({ labels: { assistant_id: assistantId } });
  }
  return createDeepAgent({
    model: "google-genai:gemini-3.1-pro-preview",
    backend: await DaytonaSandbox.connect(sandbox.id),
  });
}
助手作用域的陷阱:沙箱内部状态会随时间积累——磁盘空间耗尽、依赖冲突、缓存膨胀。务必配置 TTL、使用快照定期重置,或实现清理逻辑。
手动管理:有关在 graph factory 之外手动创建、执行和拆除沙箱的操作,请参阅基本用法和沙箱集成获取提供商特定的 API。

集成模式

两种架构模式——Agent 在沙箱内运行,或沙箱作为远程工具

Agent 在沙箱内运行

Agent 本身部署在沙箱容器里,你通过网络与其通信。

  • 与本地开发体验高度一致
  • Agent 与环境紧密耦合

权衡:

  • API 密钥必须存在于沙箱内(安全风险)
  • 更新代码需要重新构建镜像
  • 需要额外的通信基础设施
FROM python:3.11
RUN pip install deepagents-code

沙箱作为工具

Agent 在你的机器上运行,需要执行代码时调用沙箱工具。

  • 无需重建镜像即可更新 Agent 代码
  • API 密钥保留在沙箱外部
  • 可跨多个沙箱并行运行任务

权衡:

  • 每次执行调用有网络延迟
// 沙箱作为工具模式(本文档推荐)
import { DaytonaSandbox } from "@langchain/daytona";
import { createDeepAgent } from "deepagents";

const sandbox = await DaytonaSandbox.create();  // 创建新的沙箱容器

const agent = createDeepAgent({
  backend: sandbox,  // 沙箱作为后端,Agent 可执行任意 Shell 命令
  systemPrompt: "You are a coding assistant with sandbox access.",
});

try {
  const result = await agent.invoke({
    messages: [
      { role: "user", content: "Create a hello world Python script and run it" },
    ],
  });
} catch (err) {
  await sandbox.close(); // 异常时主动清理,避免资源泄漏
  throw err;
}
图:两种沙箱集成架构对比
如何选择:需要快速迭代 Agent 逻辑、把密钥留在沙箱外——选"沙箱作为工具"。需要生产环境与本地开发一致——选"Agent 在沙箱内运行"。本文档所有示例使用"沙箱作为工具"模式。

工作原理

沙箱后端只需实现一个 execute() 方法——其他文件操作都构建在它之上

架构极其简洁:提供商唯一需要实现的方法是 execute(),它运行一条 Shell 命令并返回输出。所有文件系统操作(read、write、edit、ls、glob、grep)都由 BaseSandbox 基类通过构造脚本调用 execute() 实现。

架构示意(execute 方法体系):Agent 工具(ls、read_file 等)和 execute 工具暴露给 Agent 调用;BaseSandbox 基类通过 execute() 方法实现所有文件系统操作;Provider SDK 位于最底层,负责实际执行 Shell 命令。这意味着:添加新提供商只需实现 execute() 方法;execute 工具按条件可用——不实现 SandboxBackendProtocol 的后端不会暴露 execute 工具。
层面方法谁调用用途
Agent 工具 ls、read_file、write_file 等 LLM 在执行期间调用 读取代码、写入文件、搜索内容
execute 工具 execute LLM 在执行期间调用 运行 Shell 命令
底层方法 execute() BaseSandbox 基类 所有工具的底层执行
文件传输 uploadFiles() / downloadFiles() 你的应用代码 宿主机与沙箱之间移动文件
// execute() 的返回格式
// 成功:
// "4\n[Command succeeded with exit code 0]"

// 失败:
// "bash: foobar: command not found\n[Command failed with exit code 127]"

// 输出过大时自动截断,Agent 会被指示用 read_file 增量读取

添加新提供商很简单

只需实现 execute() 方法。基类 BaseSandbox 会自动提供所有文件系统工具的实现。

execute 工具按条件可用

每次模型调用时,harness 会检查后端是否实现了 SandboxBackendProtocol。如果没有,execute 工具会被过滤掉,Agent 永远看不到它。

图:execute() 分层调用链

文件操作

两个层面——Agent 的文件系统工具 vs 应用的文件传输 API

Agent 文件系统工具

read_file、write_file、edit_file、ls、glob、grep、execute

  • LLM 在执行期间调用的工具
  • 通过沙箱内的 execute() 运行
  • Agent 用来读写文件和运行命令

文件传输 API

uploadFiles()、downloadFiles()

  • 你的应用代码调用的方法
  • 使用提供商的原生传输 API(非 Shell)
  • 在宿主机与沙箱之间移动文件
架构示意(文件访问的两个层面):应用代码通过提供商 API(uploadFiles/downloadFiles)直接访问沙箱文件系统,用于预填充沙箱和获取产物;Agent 的 LLM 调用 tools(read_file、write_file 等),tools 通过 execute() 在沙箱内执行 Shell 命令来操作文件系统。两种路径各有用途:Agent 工具操作沙箱内部的文件,文件传输 API 跨越宿主机与沙箱的边界。

预填充沙箱

在 Agent 运行前注入源代码、配置或数据:

const encoder = new TextEncoder();
const responses = await sandbox.uploadFiles([
  ["src/index.js", encoder.encode("console.log('Hello')")],
  ["package.json", encoder.encode('{"name": "my-app"}')],
]);

for (const res of responses) {
  if (res.error) {
    console.error(`上传失败 ${res.path}: ${res.error}`);
  }
}

获取产物

在 Agent 完成后获取生成的文件:

const results = await sandbox.downloadFiles([
  "src/index.js", "output.txt"
]);

const decoder = new TextDecoder();
for (const result of results) {
  if (result.content) {
    console.log(
      `${result.path}: ${decoder.decode(result.content)}`
    );
  }
}
别搞混:Agent 用的是 read_file / write_file(工具),你的应用代码用的是 uploadFiles() / downloadFiles()(API)。前者在沙箱内部操作,后者跨越宿主机和沙箱边界。

安全边界

沙箱保护宿主机,但不能防护上下文注入——密钥管理是关键

沙箱能防护的

  • Agent 无法读取你的本地文件
  • Agent 无法访问宿主机环境变量
  • Agent 无法干扰宿主机其他进程
  • 沙箱故障不会影响宿主机
  • 沙箱销毁后所有痕迹消失

沙箱不能防护的

  • 上下文注入——攻击者控制 Agent 输入,可在沙箱内运行任意命令
  • 网络数据外泄——被注入的 Agent 可通过 HTTP/DNS 发送数据
  • 沙箱内密钥泄露——注入的 Agent 可读取沙箱内的凭据
最重要的一条规则:切勿将密钥放入沙箱。

通过环境变量、挂载文件或 secrets 选项注入的 API 密钥、令牌、数据库凭据,都可能被上下文注入的 Agent 读取和外泄。即使短期或限定范围的凭据也是如此——如果 Agent 能访问,攻击者也能。

安全思维:沙箱是隔离层,不是安全银弹。它保护的是你的宿主机不被 Agent 破坏,但沙箱内部的一切对 Agent 来说都是完全可访问的。因此,密钥永远不要进沙箱。

安全处理密钥

Agent 需要调 API 怎么办?两种方案——把密钥留在沙箱外面

方案一:宿主机工具(推荐)

定义在宿主机上运行的自定义工具,在宿主机处理认证。

  • Agent 通过名称调用工具
  • 工具在宿主机执行,带认证
  • Agent 永远看不到凭据
  • 最简单、最安全的方案
// 在宿主机上定义带认证的自定义工具
// 工具在宿主机执行,API 密钥永远不进入沙箱
const callApi = tool(
  async ({ endpoint, data }) => {
    // API_KEY 留在宿主机环境变量中,沙箱无法访问
    const res = await fetch(endpoint, {
      headers: { Authorization: `Bearer ${API_KEY}` },
      method: "POST",
      body: JSON.stringify(data),
    });
    return res.json();
  },
  {
    name: "call_api",
    description: "Call an authenticated API",
    schema: z.object({
      endpoint: z.string(),
      data: z.any().optional(),
    }),
  }
);

方案二:网络代理

用代理拦截沙箱的出站请求,自动附加凭据。

  • Agent 发起普通 HTTP 请求
  • 代理拦截并附加 Authorization 头
  • Agent 永远看不到密钥
  • 目前提供商支持有限
// Agent 代码(在沙箱内)
// 不知道密钥的存在,只发起普通请求
const response = await fetch(
  "https://api.example.com/data"
);
// 代理自动附加了认证头
如果必须将密钥注入沙箱(不推荐):
  • 为所有工具调用启用 human-in-the-loop 审批
  • 阻止或限制沙箱的网络访问(如 Modal 的 blockNetwork: true)
  • 使用范围最窄的凭据和最短有效期
  • 监控沙箱网络流量,关注异常出站请求
即使采取这些措施,仍然是不安全的权宜之计。

最佳实践

六条通用安全准则——从沙箱使用到输出处理的完整防护链

1. 审查沙箱输出

在应用程序中对沙箱输出采取操作之前,先审查其内容。沙箱内产生的一切都应视为不可信输入。

2. 阻断不必要的网络

不需要网络访问时,阻止沙箱的网络访问。减少数据外泄途径。

3. 过滤敏感模式

使用 middleware 过滤或脱敏工具输出中的敏感模式,防止泄露到日志或上游。

4. 视为不可信

将沙箱内产生的所有内容视为不可信输入——就像你对待用户提交的数据一样。

5. 及时关闭沙箱

沙箱持续消耗资源。使用 try/finally 确保在不再需要时立即关闭。

6. 配置 TTL

为沙箱配置 TTL,让提供商在闲置时自动删除或归档,避免资源浪费。

安全三原则:① 密钥不进沙箱;② 不需要时阻断网络;③ 沙箱输出当用户输入处理。记住这三条,就能避免大多数安全问题。

本章回顾

到这里,你已经掌握了沙箱后端的完整用法

你学到了什么

  • 沙箱后端在隔离容器中提供文件系统 + Shell 执行
  • 五种提供商:Modal、Daytona、Deno、Node VFS、LangSmith
  • 两种作用域:线程隔离 vs 助手共享
  • 两种集成模式:Agent 在沙箱内 vs 沙箱作为工具
  • execute() 是唯一需要实现的方法
  • 文件操作分两层:Agent 工具 vs 文件传输 API
  • 密钥永远不进沙箱——用宿主机工具或代理

后续章节预告

  • 解释器 — 在内存运行时中执行 JavaScript
  • 配置文件 — Agent 的配置和档案管理
  • 事件流 — 实时追踪 Agent 的执行过程
  • 流式输出 — Token 级别的实时响应
  • 前端集成 — 构建沙箱管理的 Web 界面
  • Deep Agents Code — 终端编程 Agent 的沙箱配置
核心结论:沙箱是 Deep Agents 从"只读写文件"到"安全执行任意代码"的关键桥梁。它的设计哲学是最小信任——隔离一切、密钥留外、输出审查。当你需要 Agent 运行测试、构建项目、处理数据时,沙箱就是你的安全基座。

延伸阅读

继续本课程

官方文档

沙箱提供商

  • Modal — 云端无服务器沙箱
  • Daytona — 完整开发环境沙箱
  • Deno — 轻量运行时沙箱
  • LangSmith — 托管沙箱平台