Skip to content

11. 沙箱系统概览:安全的代码执行环境

让代理安全地执行代码,保护你的系统

引言

代理可以生成代码、操作文件、运行 Shell 命令。但这也意味着巨大的安全风险——如果代理被恶意输入误导,可能会:

  • 读取你的密钥和凭据
  • 删除重要文件
  • 发起网络攻击
  • 消耗无限资源

沙箱(Sandbox)通过在代理执行环境和宿主系统之间建立隔离边界来解决这个问题。

┌─────────────────────────────────────────────────────────────┐
│                        宿主系统                              │
│                                                             │
│   ┌─────────────────────────────────────────────────────┐  │
│   │                    沙箱边界                          │  │
│   │  ┌─────────────────────────────────────────────┐   │  │
│   │  │                  代理                        │   │  │
│   │  │   • 文件系统操作 ✅                          │   │  │
│   │  │   • Shell 命令 ✅                           │   │  │
│   │  │   • 网络请求 (可限制) ✅                     │   │  │
│   │  └─────────────────────────────────────────────┘   │  │
│   │                                                     │  │
│   │   ❌ 无法访问宿主文件                               │  │
│   │   ❌ 无法读取宿主环境变量                           │  │
│   │   ❌ 无法影响其他进程                               │  │
│   └─────────────────────────────────────────────────────┘  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

沙箱隔离边界:宿主系统、沙箱边界与代理的三层防护模型

为什么需要沙箱?

安全威胁分析

威胁无沙箱有沙箱
读取系统密钥⚠️ 可能✅ 被阻止
删除重要文件⚠️ 可能✅ 隔离
执行恶意代码⚠️ 可能✅ 隔离
消耗无限资源⚠️ 可能✅ 可限制
网络数据泄露⚠️ 可能⚠️ 需配置

安全威胁对比:无沙箱环境的风险与有沙箱防护的安全态势

适用场景

编码代理

  • 克隆仓库、运行 git 命令
  • 执行构建和测试流水线
  • 运行 Docker-in-Docker
  • 自主修改和运行代码

数据分析代理

  • 加载和处理数据文件
  • 安装数据分析库(pandas、numpy)
  • 运行统计计算
  • 生成报告和可视化

沙箱在 DeepAgents 中的定位

在 DeepAgents 中,沙箱是一种特殊的后端(Backend):

┌─────────────────────────────────────────────────────────────┐
│                       后端类型                               │
│                                                             │
│   ┌─────────────┐  ┌─────────────┐  ┌─────────────┐        │
│   │ StateBackend│  │ Filesystem  │  │ StoreBackend│        │
│   │  (临时)     │  │   Backend   │  │  (持久化)   │        │
│   │             │  │  (本地)     │  │             │        │
│   │ 文件工具 ✅ │  │ 文件工具 ✅ │  │ 文件工具 ✅ │        │
│   │ execute ❌  │  │ execute ❌  │  │ execute ❌  │        │
│   └─────────────┘  └─────────────┘  └─────────────┘        │
│                                                             │
│   ┌─────────────────────────────────────────────────┐      │
│   │                   沙箱后端                       │      │
│   │                                                 │      │
│   │   文件工具 ✅  (ls, read_file, write_file...)   │      │
│   │   execute ✅   (运行 Shell 命令)                │      │
│   │   安全边界 ✅  (隔离宿主系统)                    │      │
│   └─────────────────────────────────────────────────┘      │
│                                                             │
└─────────────────────────────────────────────────────────────┘

沙箱后端提供:

  • 所有标准文件系统工具(ls、read_file、write_file、edit_file、glob、grep)
  • execute 工具(在沙箱中运行任意 Shell 命令)
  • 保护宿主系统的安全边界

后端类型对比:沙箱是唯一同时支持文件工具和execute的安全后端

集成模式

有两种将代理与沙箱集成的架构模式:

模式一:沙箱内运行代理

代理运行在沙箱内部,你通过网络与其通信。

┌─────────────────────────────────────────────────────────────┐
│                        你的应用                              │
│                           │                                 │
│                      WebSocket/HTTP                         │
│                           │                                 │
│   ┌───────────────────────▼─────────────────────────────┐  │
│   │                     沙箱                             │  │
│   │  ┌─────────────────────────────────────────────┐   │  │
│   │  │                  代理                        │   │  │
│   │  │   LLM API Key 在沙箱内 ⚠️                    │   │  │
│   │  └─────────────────────────────────────────────┘   │  │
│   └─────────────────────────────────────────────────────┘  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

优点

  • ✅ 更贴近本地开发体验
  • ✅ 代理与环境强耦合

缺点

  • 🔴 API Key 必须放在沙箱内(安全风险)
  • 🔴 更新需要重建镜像
  • 🔴 需要额外基础设施处理通信

Dockerfile 示例

dockerfile
FROM python:3.11
RUN pip install deepagents-cli

模式二:沙箱作为工具(推荐)

代理运行在你的机器/服务器上,需要执行代码时调用沙箱。

┌─────────────────────────────────────────────────────────────┐
│                        你的应用                              │
│                                                             │
│   ┌─────────────────────────────────────────────────────┐  │
│   │                      代理                            │  │
│   │   LLM API Key 在宿主 ✅                              │  │
│   │                                                     │  │
│   │   execute("npm run build")                          │  │
│   │          │                                          │  │
│   └──────────│──────────────────────────────────────────┘  │
│              │ Provider API                                 │
│   ┌──────────▼──────────────────────────────────────────┐  │
│   │                     沙箱                             │  │
│   │   执行命令并返回结果                                 │  │
│   └─────────────────────────────────────────────────────┘  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

优点

  • ✅ 无需重建镜像即可更新代理代码
  • ✅ API Key 留在沙箱外部
  • ✅ 沙箱故障不会丢失代理状态
  • ✅ 仅为执行时间付费

缺点

  • 🔴 每次执行调用有网络延迟

推荐使用此模式,本教程的示例都基于"沙箱作为工具"模式。

两种集成模式对比:沙箱内运行代理 vs 沙箱作为工具(推荐)

可用的沙箱提供商

提供商特点适用场景
ModalML/AI 工作负载,GPU 访问,Python机器学习、数据分析
DaytonaTypeScript/Python 开发,快速冷启动编码代理、团队协作
DenoJavaScript 工作负载,microVM轻量级脚本执行
Node VFS本地虚拟文件系统,无需云服务本地开发、测试

提供商选择指南

开始

  ├─ 需要 GPU?
  │    │
  │    └─ 是 ──→ Modal

  ├─ 需要快速冷启动?
  │    │
  │    └─ 是 ──→ Daytona

  ├─ 主要是 JavaScript/TypeScript?
  │    │
  │    └─ 是 ──→ Deno

  └─ 只是本地开发/测试?

       └─ 是 ──→ Node VFS

沙箱提供商选择决策树:根据需求快速选择Modal、Daytona、Deno或Node VFS

基础用法

创建沙箱代理

typescript
import { createDeepAgent } from "deepagents";
import { DenoSandbox } from "@langchain/deno";

const sandbox = await DenoSandbox.create({
  memoryMb: 1024,
  lifetime: "10m",
});

try {
  const agent = createDeepAgent({
    backend: sandbox,
    systemPrompt: "你是一个可以访问沙箱的 JavaScript 编码助手。",
  });

  const result = await agent.invoke({
    messages: [{
      role: "user",
      content: "创建一个 HTTP 服务器并测试它",
    }],
  });

  console.log(result.messages[result.messages.length - 1].content);
} finally {
  await sandbox.close();
}

execute 工具

当代理需要执行命令时,会调用 execute 工具:

typescript
execute("npm install express")

execute("npm run build")

execute("git clone https://github.com/user/repo.git")

返回结果

// 成功
npm notice created a lockfile...
[命令成功,退出码为 0]

// 失败
bash: foobar: command not found
[命令失败,退出码为 127]

文件操作

沙箱内的文件系统工具与其他后端相同:

typescript
ls("/workspace")

read_file("/workspace/package.json")

write_file("/workspace/app.js", "console.log('Hello')")

edit_file("/workspace/app.js", {
  old_string: "Hello",
  new_string: "Hello World"
})

两种文件访问方式

理解这两种文件访问方式很重要:

1. 代理文件系统工具

代理在执行过程中使用的工具(read_file、write_file 等),通过沙箱内的 execute() 运行。

typescript
// 代理调用
read_file("/workspace/code.js")
write_file("/workspace/output.txt", "结果")

2. 文件传输 API

你的应用代码调用的 uploadFiles()downloadFiles() 方法,用于在宿主和沙箱之间传输文件。

typescript
// 你的应用代码

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

// 运行代理...

// 取回产物
const results = await sandbox.downloadFiles([
  "dist/bundle.js",
  "report.html"
]);

使用场景

方法使用者用途
read_file/write_file代理执行任务时读写文件
uploadFiles应用代码在代理运行前准备文件
downloadFiles应用代码在代理结束后取回产物

两种文件访问方式:代理文件系统工具与文件传输API的工作流程

生命周期管理

基础生命周期

typescript
// 1. 创建并初始化
const sandbox = await ModalSandbox.create(options);

// 2. 使用沙箱
const result = await sandbox.execute("echo hello");
// 或通过代理使用
const agent = createDeepAgent({ backend: sandbox });
await agent.invoke(messages);

// 3. 清理
await sandbox.close();

按对话生命周期

在聊天应用中,通常每个 thread_id 使用一个独立的沙箱:

typescript
import { v4 as uuidv4 } from "uuid";
import { Daytona } from "@daytonaio/sdk";
import { DaytonaSandbox } from "@langchain/daytona";
import { createDeepAgent } from "deepagents";

const client = new Daytona();
const threadId = uuidv4();

let sandbox;
try {
  sandbox = await client.findOne({ labels: { thread_id: threadId } });
} catch {
  sandbox = await client.create({
    labels: { thread_id: threadId },
    autoDeleteInterval: 3600,
  });
}

const backend = await DaytonaSandbox.fromId(sandbox.id);
const agent = createDeepAgent({
  backend,
  systemPrompt: "你是一个编码助手。",
});

try {
  const result = await agent.invoke(
    { messages },
    { configurable: { thread_id: threadId } }
  );
} catch (err) {
  await client.delete(sandbox);
  throw err;
}

TTL 配置

为聊天应用配置存活时间(TTL),自动清理空闲沙箱:

typescript
const sandbox = await DaytonaSandbox.create({
  autoDeleteInterval: 3600,  // 1小时后自动删除
});

const sandbox = await DenoSandbox.create({
  lifetime: "10m",  // 10分钟后过期
});

安全注意事项

隔离边界能做什么

沙箱能保护

  • 宿主文件系统
  • 宿主环境变量
  • 其他进程

隔离边界不能做什么

⚠️ 沙箱无法防御

上下文注入攻击:如果攻击者能控制代理输入的一部分,就可以指示代理在沙箱内执行任意命令。

网络数据泄露:除非阻断网络访问,否则被注入的代理可以通过 HTTP 或 DNS 将数据发送出沙箱。

🚨 永远不要把密钥放进沙箱

┌────────────────────────────────────────────────────────────┐
│  🔴 严重警告                                               │
│                                                            │
│  永远不要把以下内容放入沙箱:                               │
│  • API Key                                                 │
│  • 数据库凭据                                              │
│  • Token                                                   │
│  • 通过环境变量注入的密钥                                   │
│  • 通过文件挂载的凭据                                       │
│                                                            │
│  原因:                                                    │
│  遭受上下文注入的代理可以读取并外泄这些密钥。                │
│  即使是短生命周期的凭据也一样。                              │
└────────────────────────────────────────────────────────────┘

安全地处理密钥

方案一:将密钥保留在沙箱外的工具中(推荐)

typescript
const callAuthenticatedAPI = tool(
  async ({ endpoint, params }) => {
    const response = await fetch(endpoint, {
      headers: {
        Authorization: `Bearer ${process.env.API_KEY}`,  // 在宿主环境
      },
      body: JSON.stringify(params),
    });
    return response.json();
  },
  {
    name: "call_api",
    description: "调用需要认证的 API",
    schema: z.object({
      endpoint: z.string(),
      params: z.object({}),
    }),
  }
);

const agent = createDeepAgent({
  backend: sandbox,
  tools: [callAuthenticatedAPI],  // 工具运行在宿主环境
});

方案二:使用注入凭据的网络代理

某些提供商支持代理拦截 HTTP 请求,在转发前附加凭据。代理看不到密钥。

通用最佳实践

实践说明
复核沙箱输出在采取行动前验证输出
阻断不必要的网络使用 blockNetwork: true 等选项
使用中间件过滤输出脱敏敏感模式
视沙箱产出为不信任输入不要直接使用未验证的输出

沙箱安全最佳实践:四大安全原则与推荐的密钥处理方案

沙箱 vs LocalShellBackend

特性LocalShellBackend沙箱后端
执行环境宿主机隔离容器
安全性⚠️ 危险✅ 安全
访问宿主文件✅ 可以❌ 隔离
性能稍慢(网络延迟)
适用场景本地开发(自己使用)生产环境
成本按使用付费

选择建议

  • 本地开发、自己使用 → LocalShellBackend(配合 interrupt_on)
  • 生产环境、处理用户输入 → 沙箱后端

小结

本文介绍了 DeepAgents 沙箱系统的核心概念:

概念说明
沙箱隔离的代码执行环境
隔离边界保护宿主系统不受代理影响
execute 工具在沙箱中运行 Shell 命令
两种集成模式"沙箱内运行代理" vs "沙箱作为工具"
提供商Modal、Daytona、Deno、Node VFS

安全原则

  • ✅ 使用沙箱隔离代码执行
  • ✅ 密钥保留在沙箱外
  • ✅ 阻断不必要的网络访问
  • ✅ 视沙箱输出为不信任

下一步

在下一篇文章中,我们将实战配置 Modal 和 Daytona 沙箱,构建完整的安全代码执行平台。

实践任务

  1. 使用 Deno 沙箱创建一个简单的代码执行代理
  2. 测试沙箱的隔离性:尝试在沙箱内读取宿主文件
  3. 实现文件上传/下载:为沙箱准备代码并取回产物

参考资源

读文档、看源码、写代码,理解 AI Agent 本质 🤖