Wasmtime 沙箱配置指南:最小权限原则实践
为什么选择 Wasmtime
Vigils 运行不可信的 MCP Server。传统选择是 Docker 容器,但 MCP Server 通常是单文件 WASM 模块,Docker 的启动开销(秒级)和镜像体积(百 MB 级)是过度杀伤。
Wasmtime 提供:
- 毫秒级启动:适合 Agent 工具调用的 latency 要求
- 精细资源控制:CPU、内存、燃料(fuel)均可量化限制
- WASI 能力模型:通过 capability 显式授权,默认无任何权限
- 确定性执行:WASM 的 sandbox 边界由 VM 保证,不依赖 OS 命名空间
从 wasmtime 25 升级到 43.0.2 的过程中,我们修复了 15 个 RUSTSEC advisory,涉及内存安全、资源泄漏和依赖链漏洞。
默认拒绝策略
Vigils 沙箱的默认配置是"零权限":
network: deny
filesystem: deny
env: clear (env_clear)
cpu_limit: 1 core
memory_limit: 128 MiB
wall_time: 30s
任何扩权都必须显式声明。例如一个需要读取临时目录的 MCP Server:
vigils-hub add-local-mcp ./server.wasm \
--allow-dir /tmp/mcp-work \
--cpu-ms 5000 \
--mem-mb 64
没有 --allow-dir,Server 对文件系统的访问会被 WASI 层直接拒绝。
资源限制实战
CPU 限制
使用 wasmtime 的 fuel 机制:每条 WASM 指令消耗固定 fuel,耗尽即 trap。
let mut config = Config::new();
config.consume_fuel(true);
let engine = Engine::new(&config)?;
let mut store = Store::new(&engine, ());
store.add_fuel(10_000_000_000)?; // 约 10s 单核等价
内存限制
通过 StoreLimits 设置线性内存和 table 的上限:
store.limiter(|state| &mut state.limits);
超过限制时,WASM 的 memory.grow 返回 -1,由 Guest 决定如何处理(通常 panic)。
时间限制
使用 async + tokio::time::timeout 包装 Store 的执行。对于同步路径,Vigils 使用独立进程 + waitpid timeout,防止任何阻塞调用逃逸。
Linux Landlock LSM
Vigils 在 Linux 上使用 Landlock 为沙箱增加第二道防线。这是整个 workspace 中唯一允许 unsafe_code 的地方(ADR 0007 §I-7.8 明确批准)。
Landlock 在 exec 前通过 pre_exec 钩子设置自限制规则:
// vigils-sandbox-linux/src/landlock.rs
command.pre_exec(|| {
let ruleset = create_ruleset()?;
restrict_self(ruleset)?; // unsafe: pre_exec 要求 async-signal-safe
Ok(())
});
即使 WASI 层被绕过(例如通过原生代码编译的 WASM),Landlock 也会在内核层拒绝未授权的文件/网络访问。
RUSTSEC 修复清单
wasmtime 25 → 43.0.2 的升级修复了以下类别的漏洞:
- 内存安全:WASM 线性内存边界检查优化中的 off-by-one
- 资源泄漏:fuel 耗尽后未正确释放 Store 资源
- 依赖链:wasmparser 中的深度嵌套拒绝服务
- 编译器:cranelift 的 speculative execution 信息泄漏(CVE-2024-XXXX 类)
Vigils 的 cargo deny 配置要求所有依赖必须通过 RUSTSEC 扫描,每季度更新一次基线。
监控与应急
沙箱运行时的监控指标:
sandbox.exec.duration_ms—— 识别异常慢的执行sandbox.mem.peak_mb—— 内存泄漏检测sandbox.fuel.remaining—— 燃料消耗模式分析sandbox.landlock.violations—— Landlock 拦截事件(应为 0)
当沙箱进程超出任何限制时,Vigils 执行以下动作:
- 发送 SIGKILL(不可捕获)
- 记录 DecisionRecord,decision = Deny,reason = SandboxLimitExceeded
- 触发审计事件
sandbox.exec_killed - 向 Agent 返回结构化错误,不暴露内部限制值
与其他沙箱技术对比
| 技术 | 启动延迟 | 资源开销 | 安全边界 | 适用场景 |
|---|---|---|---|---|
| Docker | 1-3s | 百 MB | Namespace + seccomp | 长期服务 |
| gVisor | 2-5s | 百 MB | 用户态内核 | 不可信多租户 |
| Firecracker | 100-300ms | 数十 MB | KVM | Serverless |
| seccomp-bpf | 0ms | 0 | 系统调用过滤 | 辅助手段 |
| Wasmtime + Landlock | <10ms | <5 MB | VM + LSM | Agent 工具调用 |
对于 MCP Server 这种"高频、轻量、不可信"的 workload,Wasmtime + Landlock 是当前最优解。