第 11 章 · Sandboxes 安全沙箱
本章目标:理解"agent 没有环境除非你给它一个",掌握
useSandbox()、local()本地沙箱、虚拟与远程容器沙箱的区别,学会设计安全边界。
11.1 沙箱给 agent 带来什么
一个 agent 至多拥有一个执行环境,而且默认没有——不调用 useSandbox() 它就只能纯文本对话。挂上沙箱后,agent 的工具集一次性获得:
text
read / write / edit / bash / grep ← 文件与 shell 能力typescript
// agents/researcher.ts
// 一个只读网络、不碰文件系统的 agent:不挂沙箱
'use agent';
import { useModel } from '@flue/runtime';
export function Researcher() {
useModel('anthropic/claude-sonnet-4-6');
return '你是资料调研员,仅基于对话中的材料回答问题。';
}typescript
// agents/coder.ts
// 需要读写代码、跑命令的 agent:挂本地沙箱
'use agent';
import { useModel, useSandbox } from '@flue/runtime';
import { local } from '@flue/runtime/node';
export function Coder() {
useModel('anthropic/claude-sonnet-4-6');
useSandbox(local()); // 绑定宿主机文件系统
return '你是修复 Bug 的工程师,可以直接修改仓库文件并运行测试。';
}11.2 三种沙箱形态
Flue 提供三种粒度递增的隔离方式:
| 形态 | API | 隔离级别 | 典型场景 |
|---|---|---|---|
| 虚拟(内存) | 内置 virtual sandbox | 无系统调用 | 教学、纯文本文件实验 |
| 本地 | local() | 宿主机权限 | 个人开发、可信脚本 |
| 远程容器 | 远程 provider(如 Daytona) | 容器/微 VM 隔离 | 生产、运行不可信代码 |
typescript
// 虚拟沙箱:内存中的假文件系统
'use agent';
import { useSandbox } from '@flue/runtime';
import { virtual } from '@flue/runtime/sandbox';
export function NoteTaker() {
useModel('anthropic/claude-sonnet-4-6');
// 内存虚拟 FS:进程结束即消失,绝不触碰真实磁盘
useSandbox(virtual({
files: {
'notes.md': '# 会议记录\n- 待补充',
},
}));
return '帮用户维护一份 Markdown 笔记。';
}typescript
// 远程容器沙箱:把不可信的执行丢进隔离容器
'use agent';
import { useSandbox } from '@flue/runtime';
import { daytona } from '@flue/runtime/daytona'; // 以 Daytona 为例
export function UntrustedRunner() {
useModel('anthropic/claude-sonnet-4-6');
// 每次任务在远端容器中执行,崩溃或恶意行为不影响宿主机
useSandbox(daytona({ image: 'node:22-slim', timeoutMinutes: 30 }));
return '在沙箱里运行用户提交的代码并汇报输出。';
}11.3 环境随生命周期变化
文档强调 "an agent's environment can change over its life"——沙箱不是一次性的。典型模式:开发时用 local() 快速迭代,生产切换远程容器。
typescript
// 按部署环境选择沙箱实现
'use agent';
import { useSandbox } from '@flue/runtime';
import { local } from '@flue/runtime/node';
import { daytona } from '@flue/runtime/daytona';
export function Worker() {
const env = process.env.DEPLOY_ENV ?? 'dev';
if (env === 'prod') {
// 生产:容器隔离 + 超时保护
useSandbox(daytona({ image: 'node:22-slim', timeoutMinutes: 60 }));
} else {
// 开发:直接用本机,调试方便
useSandbox(local());
}
return '按环境自动选择执行环境的通用 worker。';
}11.4 设计安全边界
沙箱是安全边界的第一道墙。设计时问三个问题:
- 这个 agent 最少需要什么能力? 只读调研就不该有
write; - 它处理的输入可信吗? 处理用户提交代码必须上容器;
- 爆炸半径多大?
local()的边界就是你的用户权限——生产别用。
typescript
// 边界示例:最小权限组合
'use agent';
import { useSandbox, useTool } from '@flue/runtime';
import { local } from '@flue/runtime/node';
import { fetchDocs } from '../tools/docs.ts';
export function Auditor() {
// 审计 agent:能读代码找问题,但没有任何写工具
useSandbox(local());
// 自定义工具也遵循最小权限:fetchDocs 内部只发 GET
useTool(fetchDocs);
return '审计当前仓库的安全隐患,只报告不修改。';
}与 Pi 的对照
Pi 官方明确表示自身不带权限系统、默认继承启动者权限,建议用容器化兜底;Flue 把这一层做成了框架内建(virtual/local/remote),但选错形态同样危险——local() 不等于安全。
11.5 本章小结
- 默认 agent 没有环境;
useSandbox()才赋予 read/write/edit/bash/grep; - 三种形态:virtual(内存)、
local()(宿主机)、远程容器(Daytona 等); - 环境 can change over its life——按 DEPLOY_ENV 切换是常见模式;
- 安全边界三问:最少能力?输入可信?爆炸半径?
🧪 随堂测验
点击你认为正确的选项。答错时会展示正确答案与原因解析。
1. 一个没有调用 useSandbox() 的 Flue agent 能做什么?
2. local() 沙箱的隔离级别是?
3. 你的 agent 要运行用户提交的任意 Python 代码,应选择哪种沙箱?
4. 关于 agent 环境的生命周期,正确的说法是?
🛠️ 动手实践
- 写一个
NoteTakeragent 使用虚拟沙箱,验证进程重启后 notes.md 是否还在,并解释原因。 - 把第 10 章 GitHub 分诊 agent 的沙箱从
local()切换为 Daytona 远程容器,对比两种配置下的执行差异。 - 为一个需要读日志但不能写文件的监控 agent 设计最小权限方案,说明哪些工具应该缺失。