Skip to content

第 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 设计安全边界

沙箱是安全边界的第一道墙。设计时问三个问题:

  1. 这个 agent 最少需要什么能力? 只读调研就不该有 write
  2. 它处理的输入可信吗? 处理用户提交代码必须上容器;
  3. 爆炸半径多大? 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 环境的生命周期,正确的说法是?

🛠️ 动手实践

  1. 写一个 NoteTaker agent 使用虚拟沙箱,验证进程重启后 notes.md 是否还在,并解释原因。
  2. 把第 10 章 GitHub 分诊 agent 的沙箱从 local() 切换为 Daytona 远程容器,对比两种配置下的执行差异。
  3. 为一个需要读日志但不能写文件的监控 agent 设计最小权限方案,说明哪些工具应该缺失。