BoxAgnts 运行时(3)——WebAssembly:更好的 Agent 沙箱
AI Agent 正日益超越文本生成。现代 Agent 系统可以执行代码、操作文件、浏览网页、调用 API、管理基础设施、协调分布式任务。一旦 Agent 开始与真实环境交互,执行安全就从 Prompt 问题变成了系统级问题。
大多数当前实现依赖 Python 子进程、Shell 命令和容器隔离——这些方法为人类控制的软件设计,不适合 LLM 驱动的概率性执行系统。
WebAssembly 正在成为最强候选。不是因为时髦,而是因为它的执行语义与 AI 基础设施的安全要求惊人地契合。
传统 Agent 执行的问题
大多数 Agent 运行时最终收敛到熟悉的架构:
LLM → 工具调用 → Python 运行时 → Shell / 文件系统 / 网络
传统工具执行引入了一系列顽疾:不受限的主机交互、依赖冲突、环境不一致、薄弱的隔离边界、困难的资源治理。当执行决策来自 LLM 时,问题更加严重——LLM 对提示词操控敏感、执行路径概率性、外部上下文可改变行为。
BoxAgnts 直接放弃了这种架构。看一下 boxagnts/wasm-tools/src/wasm_tool.rs——每个工具都是一段独立的 WASM 模块:
pub struct WasmTool {
name: String,
wasm_file: String, // WASM 二进制文件路径
description: String,
permission_level: PermissionLevel,
input_schema: Value,
}
这不是"在 Python 里调用 Shell"——这是"在受控沙箱里执行自包含的二进制模块"。两者的安全边界天差地别。
容器有帮助,但还不够
容器提供了文件系统分离、进程命名空间、网络隔离和可重现部署。但它们仍然暴露相对宽泛的执行面——即使容器内,Agent 仍可能滥用工具、访问非预期资源、泄露数据、递归调用危险操作。
容器回答:"这个进程在哪个环境中运行?"
AI 运行时必须回答:"这个 Agent 被允许执行哪些确切的操作?"
BoxAgnts 的 WASM 沙箱正是为了回答第二个问题而设计的。它不依赖操作系统的进程隔离,而是在 Wasmtime 虚拟机层面构建边界——比进程更细粒度,比容器更轻量。
WebAssembly 的安全模型
默认情况下,WASM 模块:
- 不能访问任意内存
- 不能访问文件系统
- 不能打开网络连接
- 不能生成进程
- 不能直接与主机系统交互
每一次与外部世界的交互都必须由运行时显式授予。
这个"默认拒绝"模型与 AI Agent 的安全需求天然契合。BoxAgnts 的 RunOption 结构体是这一哲学的代码体现:
// boxagnts/wasm-sandbox/src/run.rs
pub struct RunOption {
pub work_dir: Option<String>,
pub map_dirs: Option<Vec<(String, String)>>,
pub env_vars: Option<Vec<(String, Option<String>)>>,
pub allowed_outbound_hosts: Option<Vec<String>>,
pub block_url: Option<String>,
pub block_networks: Option<Vec<String>>,
pub wasm_timeout: Option<u32>,
pub wasm_max_memory_size: Option<u32>,
pub wasm_max_wasm_stack: Option<u32>,
pub wasm_fuel: Option<u32>,
pub wasm_cache_dir: Option<String>,
}
每一项都是显式授予。不配置 work_dir?WASM 模块看不到任何文件。不配置 allowed_outbound_hosts?所有网络请求被拦截。不配置 wasm_timeout?长时间运行会被终止。
能力注入:真正的杀手锏
现代 WASM 运行时最重要的属性不是可移植性,而是能力注入(Capability Injection)。
运行时可以有选择地提供文件系统访问、网络访问、环境变量、持久存储——并带有细粒度控制:
read:/workspace/docs
write:/workspace/tmp
fetch:https://api.example.com
BoxAgnts 中的每个 WASM 工具都有自己独立的能力集合。在 WasmTool::execute 中,ToolContext 提供的配置被精确映射到 RunOption:
let work_dir = ctx.get_work_dir().await; // 只暴露工作目录
let allowed_outbound_hosts = ctx.get_allowed_outbound_hosts(); // 白名单网络
let cache_dir = ctx.get_app_cache_dir().await; // 缓存目录
模块无法超出接收到的能力范围。这与传统子进程执行有根本区别——子进程从父进程继承权限,WASM 模块从零开始。
确定性执行
现代 Agent 经常动态安装依赖、修改运行时状态、生成临时代码——这让可重现性变得几乎不可能。
WebAssembly 模块是自包含的、平台无关的、运行时受限的、显式授权的。这意味着同一段 WASM 工具在任何地方行为一致——本地开发机、云服务器、边缘设备、甚至浏览器。
BoxAgnts 内置了 7 个 WASM 工具,全部位于 app/extensions/tools/ 目录下:
file-read-component.wasm ← 文件读取(支持大文件分页)
file-write-component.wasm ← 文件写入
file-edit-component.wasm ← 精确字符串替换编辑
file-glob-component.wasm ← 文件名模式匹配
bash-component.wasm ← Shell 命令执行
web-fetch-component.wasm ← HTTP 请求
boxedjs-execute-component.wasm ← JavaScript 代码执行
每个工具编译一次,到处运行,行为一致——这对 AI 基础设施的审计、调试、重放和治理至关重要。
WASI:从执行格式到实用运行时
单独的 WASM 只是二进制格式。WASI(WebAssembly System Interface)将其扩展为实用运行时,引入文件系统、网络、时钟、随机数、流、环境变量等标准化接口。
更重要的是,WASI 围绕能力导向原则设计——资源默认不可全局访问,必须显式提供。BoxAgnts 的 WASM 运行时在 RunCommon 配置中开启 WASI 支持:
// boxagnts/wasm-sandbox/src/run.rs
run_common.common.wasi.cli = Some(true);
run_common.common.wasi.http = Some(true);
run_common.common.wasi.inherit_network = Some(true);
run_common.common.wasi.allow_ip_name_lookup = Some(true);
HTTP 不是默认开启的——需要显式设置 wasi.http = Some(true)。即使开启,出站连接仍受 allowed_outbound_hosts 和 block_networks 约束。
传统操作系统围绕可信人类用户演化。AI Agent 不是人类用户,LLM 无法可靠区分敏感文件、特权 API 和生产基础设施。AI 系统需要比传统软件更严格、更细粒度的执行边界。
资源治理
现代 Agent 可以生成惊人地不稳定的工作负载——递归循环、过多任务、过多内存、失控 API 流量。BoxAgnts 通过 WASM 运行时提供多层资源治理:
wasm_timeout → 防止长时间运行
wasm_max_memory_size → 防止内存膨胀
wasm_max_wasm_stack → 防止栈溢出
wasm_fuel → 指令计数限制(类似 gas)
wasm_fuel 是一个特别精巧的设计——每个 WASM 指令消耗 1 单位燃料,耗尽后执行被捕获终止。这与区块链的 gas 机制原理相同,可以有效防止无限循环和 DoS 攻击。
多 Agent 隔离
BoxAgnts 的 Managed Agent 模式支持多个 Executor 并行运行——每个在自己的 WASM 沙箱中,独立内存、独立能力、独立生命周期。
这种隔离不是事后才想到的——它从 RunOption 的每个实例创建时就内置在架构中。如同操作系统的进程隔离,一个 Executor 的崩溃或越权不会波及其他。
结语
WebAssembly 之所以是 AI Agent 更好的沙箱,不在于它的"新潮",而在于它的安全模型——默认零权限、能力显式注入、资源硬性约束、行为确定性。
BoxAgnts 的架构实践证明了这一路径的可行性:所有工具通过统一的 Tool trait 注册,所有 WASM 工具通过统一的 RunOption 约束执行,所有运行时风险在沙箱边界被拦截。
相关资源
- Boxagnts:github.com/guyoung/box…