沙箱(Sandboxes)
在隔离环境中安全执行代码——让 Agent 拥有 Shell 能力而不危及宿主系统
从"只读文件"到"安全执行任意命令"的关键一步为什么需要它
你的 Agent 需要运行 Shell 命令——但你能信任它吗?
没有沙箱的风险
- Agent 运行
rm -rf /?你的文件全没了 - Agent 读取
.env?密钥直接泄露 - Agent 访问内网?整个网络暴露
- Agent 安装恶意包?系统被植入后门
- 多个 Agent 同时跑?互相干扰、资源争抢
沙箱的解法
- Agent 在隔离容器中运行,碰不到宿主机
- 文件系统完全隔离,无法读取你的密钥
- 网络可配置,按需阻断外联
- 每次运行是全新环境,无残留风险
- 按执行时间付费,用完即销毁
沙箱是什么
沙箱是 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() |
沙箱后端支持 |
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 确保清理。
--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。
典型场景
沙箱特别适合这两类 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: 下载分析报告
生命周期
两种作用域——每个对话一个沙箱,或所有对话共享一个沙箱
线程作用域(默认)
每个对话获得自己的沙箱。首次运行时创建,同一线程后续交互复用。
- 线程结束或 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),
});
}
集成模式
两种架构模式——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;
}
工作原理
沙箱后端只需实现一个 execute() 方法——其他文件操作都构建在它之上
架构极其简洁:提供商唯一需要实现的方法是 execute(),它运行一条 Shell 命令并返回输出。所有文件系统操作(read、write、edit、ls、glob、grep)都由 BaseSandbox 基类通过构造脚本调用 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 永远看不到它。
文件操作
两个层面——Agent 的文件系统工具 vs 应用的文件传输 API
Agent 文件系统工具
read_file、write_file、edit_file、ls、glob、grep、execute
- LLM 在执行期间调用的工具
- 通过沙箱内的
execute()运行 - Agent 用来读写文件和运行命令
文件传输 API
uploadFiles()、downloadFiles()
- 你的应用代码调用的方法
- 使用提供商的原生传输 API(非 Shell)
- 在宿主机与沙箱之间移动文件
预填充沙箱
在 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)}`
);
}
}
read_file / write_file(工具),你的应用代码用的是 uploadFiles() / downloadFiles()(API)。前者在沙箱内部操作,后者跨越宿主机和沙箱边界。
安全边界
沙箱保护宿主机,但不能防护上下文注入——密钥管理是关键
沙箱能防护的
- Agent 无法读取你的本地文件
- Agent 无法访问宿主机环境变量
- Agent 无法干扰宿主机其他进程
- 沙箱故障不会影响宿主机
- 沙箱销毁后所有痕迹消失
沙箱不能防护的
- 上下文注入——攻击者控制 Agent 输入,可在沙箱内运行任意命令
- 网络数据外泄——被注入的 Agent 可通过 HTTP/DNS 发送数据
- 沙箱内密钥泄露——注入的 Agent 可读取沙箱内的凭据
通过环境变量、挂载文件或 secrets 选项注入的 API 密钥、令牌、数据库凭据,都可能被上下文注入的 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 的沙箱配置
延伸阅读
继续本课程
- 下一章:解释器(Interpreters) — 在内存运行时中安全执行 JavaScript
- Deep Agents Code 远程沙箱 — 终端编程 Agent 的沙箱配置
- 前端沙箱界面 — 构建沙箱管理的 Web UI
官方文档
- deepagents npm 包 — 安装和 API 参考
- GitHub 仓库 — 源码和示例
- LangChain 文档索引 — 完整的文档导航