
字节笔记本
2026年10月24日 · 约 12 分钟读完
让 Agent 跑不可信代码:Sandbox 抽象

本文是《从 0 写 Agent》系列第 32 章。这个系列基于开源项目 agents-go(OpenAI Agents SDK 的 Go 移植版),用 Go 从零实现一个生产级 Agent 运行时;本章讲「Sandbox 抽象:让 Agent 跑不可信代码」。
Agent 经常需要执行模型生成的代码,数据分析、自动化脚本、生成图表。但模型生成的代码不可信,可能有 bug、可能恶意、可能死循环。
直接在本机跑?太危险。agents-go 的
Sandbox接口定义了"隔离执行代码"的抽象,有三个后端(local/docker/ssh)。这一章讲抽象设计和一个关键的错误语义反转。
引子:为什么需要沙箱
想象 Agent 帮用户分析数据,生成了这段 Python:
import os
os.system("rm -rf /") # 模型"不小心"生成的危险代码直接在本机跑 → 灾难。 沙箱解决,在隔离环境里跑,即使代码恶意,也不影响宿主。
或者这段:
while True: print("spam") # 死循环没超时限制 → 占满 CPU/磁盘,Agent 卡死。 沙箱强制超时和资源限制。
沙箱的核心价值,"不管代码多恶意,都不能逃出沙箱伤害宿主"。

Sandbox 接口
极简(sandbox.go:24):
// sandbox/sandbox.go:24
type Sandbox interface {
Exec(ctx context.Context, req ExecRequest) (*ExecResult, error)
Close() error
}两个方法,Exec(执行)和 Close(释放资源)。 简洁到极致。
ExecRequest,执行什么
// sandbox/sandbox.go:36
type ExecRequest struct {
Cmd []string // 命令,如 ["python", "main.py"]
Files map[string]string // 要写入工作目录的文件
Stdin string // 标准输入
Env map[string]string // 环境变量
Timeout time.Duration // 超时(默认 30s)
MaxOutputBytes int64 // 输出上限(默认 1MiB)
}一次执行 = 写文件 + 跑命令 + 捕获输出。 Files 在命令执行前写入工作目录。
默认限制
// sandbox/sandbox.go:17
const DefaultTimeout = 30 * time.Second
const DefaultMaxOutputBytes int64 = 1 << 20 // 1 MiB默认 30 秒超时、1MB 输出上限。 这是"防御性默认值",即使不配置,也有基本保护。
ExecResult,执行结果
// sandbox/sandbox.go:76
type ExecResult struct {
Stdout string
Stderr string
ExitCode int
TimedOut bool
}ExitCode 非 0 不算 error,这是关键设计(下面讲)。
CodeTool:把 Sandbox 变成 Agent 工具
CodeTool(sandbox/tool.go:62)把任意 Sandbox 包装成 Agent 工具:
sb, _ := docker.New(docker.Options{Image: "python:3.12-slim"})
agent := &agents.Agent{
Name: "coder",
Tools: []agents.Tool{
sandbox.CodeTool(sb, sandbox.CodeToolConfig{Name: "run_python"}),
},
}Agent 调 run_python 工具 → CodeTool 把代码写进沙箱 → 执行 → 返回 stdout/stderr。
关键设计:错误语义反转
这是 Sandbox 最有意思的设计。CodeTool 的错误处理(tool.go:84-89):
// sandbox/tool.go:84(概念)
if err != nil { // 基础设施错误(Docker 挂了、容器启动失败)
return nil, err // → abort 整个 run
}
// ExitCode 非 0(代码本身的错误)
return &ExecResult{ExitCode: result.ExitCode, Stdout: result.Stdout, Stderr: result.Stderr}, nil
// → 作为"正常输出"喂回模型,让模型修代码两种错误,两种处理:
| 错误类型 | 例子 | 处理 |
|---|---|---|
| 基础设施错误 | Docker 容器启动失败、SSH 连不上、沙箱崩溃 | abort run(模型修不了) |
| 代码错误 | exit code 非 0、语法错、运行时异常 | 回喂模型(模型可以修代码) |
为什么这样分?
- 基础设施错误是"系统级"的,Docker 挂了,模型再怎么改代码也没用。应该 abort,让人处理。
- 代码错误是"模型可修的",
SyntaxError、NameError,模型看到 stderr 能自己改代码重试。
这和第 29 章的 MCP 错误策略形成对比,MCP 错误默认全回喂,Sandbox 分两种。因为语义不同,MCP 工具的错误大多是"逻辑层"的(可自愈),Sandbox 的错误可能是"基础设施层"的(不可自愈)。
错误信息友好化
代码错误回喂时,不是直接丢 stderr,而是格式化成模型易懂的消息(tool.go:130 的 truncate):
代码执行失败(exit code 1):
Traceback (most recent call last):
File "main.py", line 3
print(x
^
SyntaxError: unexpected EOF模型看到这个,知道是语法错误,下轮会修。 而且 stdout/stderr 用 UTF-8 安全截断(truncate 不切断多字节字符),防止输出含中文时被截成半个字。
三个后端
| 后端 | 隔离性 | 适合 |
|---|---|---|
| LocalSandbox | ❌ 无隔离(直接在本机跑) | 可信代码、开发测试 |
| Docker | ✅ 强隔离(容器) | 生产、不可信代码 |
| SSH | ⚠️ 无本地隔离(但跑在远程机器) | 卸载到一次性 VM |
LocalSandbox
sb := sandbox.NewLocal()无隔离,直接在本机跑。只做了最小环境(PATH/HOME/TMPDIR,防 secret 泄漏)。明确标注"仅供可信代码/测试"。
它有个跨平台细节,Unix 上用 Setpgid 启动进程组(local_unix.go),超时时 kill 整个组防孙进程存活。这是 Unix 进程管理的经典技巧。
Docker(第 33 章详讲)
sb, _ := docker.New(docker.Options{Image: "python:3.12-slim"})强隔离,容器内跑。默认无网络、只读 fs、丢弃 capabilities、非 root。第 33 章详讲纵深防御清单。
SSH
sb, _ := ssh.New(ssh.Options{Host: "vm.example.com", User: "agent"})远程执行,代码传到远程机器跑(SFTP 传文件)。无本地隔离(代码在远程机器上自由跑),但好处是,即使代码搞破坏,也是搞坏远程的一次性 VM,不影响你的本机。
独立 module 设计
和 sessions 包一样,Docker 和 SSH 后端是独立 module(sandbox/docker、sandbox/ssh):
sandbox/ # 核心接口 + LocalSandbox(零依赖)
sandbox/docker/ # 独立 module(依赖 Docker SDK)
sandbox/ssh/ # 独立 module(依赖 golang.org/x/crypto/ssh)不用 Docker/SSH 的人,只 go get sandbox,零额外依赖。 用 Docker 的人额外 go get sandbox/docker。这是 agents-go 一贯的"重依赖隔离"原则(第 10 章讲过)。
小结
读完这一章,你应该理解:
- Sandbox 接口极简,Exec + Close,ExecRequest 携带命令/文件/超时/限制
- 默认防御,30s 超时、1MB 输出上限
- 错误语义反转,基础设施错 abort,代码错回喂(模型可修)
- 三个后端,Local(无隔离)、Docker(强隔离)、SSH(远程)
- 独立 module,Docker/SSH 依赖不污染核心
Sandbox 让 Agent 安全地执行不可信代码。 下一章深入 Docker 后端的纵深防御清单。
下一篇
第 33 章,Docker 沙箱:纵深防御清单。Docker 后端的每一行 HostConfig 都有注释,无网络、只读 fs、CapDrop、nobody 用户、PID 限制、gVisor……这是容器安全的教科书级实现。
本文是《从 0 写 Agent》系列第 32 章,案例代码基于开源项目 agents-go(OpenAI Agents SDK 的 Go 移植版)。全系列目录见总览。



