在 AI 代理(Agent)技术栈中,允许大模型生成并执行代码以调用工具,是提升其自主性与效率的关键。然而,传统的 subprocess 或 exec() 执行方式,如同 LangChain 早期因 CVE-2025-68664 所暴露的问题,本质上是将主机的完全控制权交给了不可信的生成代码。Docker 容器提供了一定隔离,但引入了沉重的运维负担。Amla Sandbox 提出了一条新路径:利用 WebAssembly(WASM)及其系统接口(WASI),在进程内构建一个轻量级、强隔离的沙盒环境,特别支持 Bash Shell 与 JavaScript 的执行。本文旨在穿透其高层特性,深入剖析其实现强隔离的两个核心底层机制:系统调用(syscall)拦截与虚拟文件系统(VFS)的权限控制,并探讨其基于能力(Capability)的安全模型如何在此之上构建可信边界。
隔离基石:WASM 内存安全与 WASI 系统调用虚拟化
Amla Sandbox 的隔离性并非源于魔法,而是建立在 WebAssembly 与生俱来的安全特性之上。WebAssembly 采用线性内存模型,所有内存访问均经过严格的边界检查,沙盒内的代码无法直接寻址或操作宿主进程的内存空间,这从原理上杜绝了内存破坏型攻击。然而,一个有用的沙盒必须能与外界交互,这就引入了系统调用(syscall)。
这正是 WASI(WebAssembly System Interface)的角色。WASI 为 WASM 模块定义了一套与操作系统交互的标准化、沙盒化的接口。Amla Sandbox 利用 WASI,对沙盒内代码发出的所有系统调用进行拦截和虚拟化。例如,当沙盒内的 Bash 脚本尝试执行 write 系统调用写入文件时,该调用并不会直接抵达宿主操作系统,而是被 WASI 层捕获。随后,请求被传递给沙盒的能力安全模型进行策略校验。只有通过校验的请求,才会由宿主环境代理执行,并将结果通过 WASI 接口返回给沙盒。这种 “拦截 - 校验 - 代理” 的模型,将不可信的代码完全置于一个由接口定义的牢笼之中。
能力安全模型:从任意授权到最小特权
系统调用拦截是手段,而判断拦截后 “放行与否” 的依据则是安全策略。Amla Sandbox 没有采用传统的基于身份或角色的访问控制(RBAC),而是采用了更符合分布式与不可信环境的 ** 能力安全(Capability-based Security)** 模型。其核心思想是:权限(能力)是不可伪造的、必须显式传递的对象。
在 Amla Sandbox 中,每一个可供 AI 代理调用的工具(如 stripe.charges.create),都必须被显式地授予沙盒,并附带一系列约束条件。这些约束构成了一个丰富的领域特定语言(DSL)。例如,开发者可以这样定义能力:
MethodCapability(
method_pattern="stripe/charges/*",
constraints=ConstraintSet([
Param("amount") <= 10000,
Param("currency").is_in(["USD", "EUR"]),
]),
max_calls=100,
)
这意味着沙盒内的代码仅被允许调用匹配 stripe/charges/* 模式的方法,且每次调用的 amount 参数不得超过 10000,currency 只能是美元或欧元,同时总调用次数上限为 100 次。这种设计将 seL4 微内核中 “无 Ambient Authority” 的理念带到了应用层:AI 代理没有任何默认权限,其所能做的每件事,都是开发者通过能力对象明确授予的。这极大地限制了提示注入(Prompt Injection)攻击成功后的破坏半径,因为攻击者无法调用任何未预先授权和约束的工具。
虚拟文件系统:路径隔离与写操作收束
文件系统访问是系统调用的重灾区,也是沙盒必须严加管控的领域。Amla Sandbox 通过 WASI 提供了一个完整的虚拟文件系统(VFS)。这个 VFS 与宿主文件系统隔离,其结构和内容由沙盒运行时管理。关键的隔离策略体现在路径的读写权限上:
- 根目录(
/)只读:沙盒内代码无法在根目录创建、修改或删除任何文件。这防止了其对虚拟文件系统结构的破坏。 - 可写目录收束:仅有两个预定义的目录允许写操作:
/workspace和/tmp。所有由 AI 代理代码产生的持久化或临时文件,都必须被限制在这两个 “沙箱中的沙箱” 内。例如,fs.writeFile('/workspace/data.json', '{}')可以成功,而fs.mkdir('/mydir')会抛出EACCES: Permission denied错误。
这种设计实现了有效的 “纵深防御”。即使攻击者通过某种方式突破了上层逻辑,试图通过文件系统进行横向移动或持久化驻留,其活动空间也被严格限制在 /workspace 和 /tmp 这两个易于监控和清理的目录内。
执行模型:Yield/Resume 与协作式安全检查
Amla Sandbox 的执行流程精巧地体现了隔离与控制的协作。沙盒本身运行在一个 WASM 实例中,内部通过 QuickJS 引擎执行 JavaScript 或模拟的 Shell 命令。其核心是一个 Async Scheduler。当沙盒内代码执行到一个工具调用(这最终会归结为一个或多个 WASI 系统调用)时,沙盒并不会阻塞等待,而是执行一次 yield 操作,将执行权连同调用请求一起交还给外部的 Python 宿主进程。
此时,Python 宿主获得控制权,它首先对 yield 出来的请求进行能力验证,检查其是否匹配已授予的能力及约束。只有验证通过,宿主才会真正执行这个工具调用(例如,调用真实的 Stripe API)。执行完毕后,宿主将结果通过 resume 调用交还给沙盒,沙盒继续执行后续代码。这个 “沙盒 yield → 宿主校验执行 → 宿主 resume 沙盒” 的循环,确保了所有对外部资源的访问,都必须经过宿主环境这一可信计算基(TCB)的审查。正如其架构图所示,所有潜在的 “危险动作” 都被迫穿过沙盒边界,在拥有完全控制权的宿主侧完成安全裁决。
工程化参数、监控清单与风险权衡
在考虑引入此类沙盒时,工程师应关注以下可落地的参数与监控点:
安全参数配置清单:
- 能力定义:为每个工具方法定义精确的
method_pattern(支持*和**通配)。 - 约束规则:明确数值型参数(如
amount)的范围、枚举型参数(如currency)的白名单、字符串参数(如path)的前缀限制。 - 调用配额:为每个能力设置
max_calls,防止资源耗尽型攻击。 - 文件系统白名单:确认业务逻辑仅需使用
/workspace和/tmp;审查是否有代码尝试突破此限制。
运行时监控要点:
- 能力使用审计:日志记录所有 yield 出的工具调用及其参数,用于事后审计和异常检测。
- 约束违规警报:任何违反参数约束的调用尝试都应触发实时警报,这可能是提示注入攻击的信号。
- 循环挂起检测:由于沙盒的 step limit 仅针对 WASM yield,不限制 JS 指令,一个
while(true){}循环会挂起线程。需在宿主侧设置执行超时(Timeout)并强制终止沙盒实例。
当前方案的明确权衡与风险:
- 优势:进程内隔离,无需额外基础设施;基于能力的安全模型,提供细粒度、可组合的权限控制;将多次工具调用合并为一次脚本执行,提升 Token 使用效率。
- 限制与风险:
- 非完全操作系统隔离:不提供完整 Linux 环境,无法运行依赖特定原生库或内核特性的代码。
- 无限循环风险:如前所述,需要宿主侧超时机制作为补充防护。
- 供应链风险:其核心的 WASM 运行时二进制目前是专有代码,这带来了潜在的供应链安全与可审计性问题。社区中也存在类似尝试,如 simonw 的
denobox和 sd2k 基于componentize-py的eryx项目,它们提供了不同的开源实现路径。 - 功能折衷:无网络访问、无 GPU 加速,适用于工具调用编排与数据处理,不适用于需要复杂计算或外部通信的场景。
结论
Amla Sandbox 通过将 WebAssembly 的内存安全隔离、WASI 的系统调用拦截虚拟化、能力安全模型的权限最小化原则以及虚拟文件系统的路径隔离相结合,为 AI 代理执行不可信代码构建了一道多层次、可验证的安全防线。它并非追求模拟一个完整的操作系统,而是专注于为 “工具调用” 这一特定场景提供恰到好处的隔离。其 yield/resume 执行模型巧妙地将安全决策点置于可信的宿主环境,确保了沙盒的不可信性不会向外扩散。对于正在寻找介于危险 exec() 与笨重 Docker 容器之间解决方案的工程师而言,理解其系统调用拦截与 VFS 隔离的底层机制,是评估其适用性、正确配置安全参数和规避潜在风险的前提。在提示注入等威胁尚未得到根本解决的当下,这种深度防御策略为构建更稳健的 AI 应用提供了关键的基础设施支撑。
资料来源
- amla-sandbox GitHub 仓库: https://github.com/amlalabs/amla-sandbox
- Hacker News 相关讨论: https://news.ycombinator.com/item?id=46824877