乐于分享
好东西不私藏

WebAssembly 成为 AI 工具的安全方案:从 Wassette 看 MCP 隔离

WebAssembly 成为 AI 工具的安全方案:从 Wassette 看 MCP 隔离

这张图来自 Wassette 官方仓库,清晰展示了它的核心架构。左边是 AI Agent(Claude、Copilot 等),右边是 WASM 组件,而中间连接两者的就是 Wassette MCP Server。


一个被忽视的问题

你的 AI 助手突然可以读取你电脑上的任意文件——不是电影情节,是真实的安全隐患。

当一个 AI Agent 具备了十几个工具能力时,这些工具之间的权限差异可能是天壤之别:有的只需要知道现在几点了,有的需要访问你的私人代码仓库,有的需要向外部网络发送请求。但传统 MCP 的实现方式,并没有对这种差异做出任何区分。

这篇文章从微软的一个开源项目出发,聊聊 WebAssembly[1] 正在如何重新定义 AI 工具的安全边界。


Wassette:它是什么?

Wassette[2](读作 “Wass-ette”)是微软出的一个开源 MCP Server 实现,名字是 Wasm + Cassette(磁带)的组合——磁带代表一种可加载、可分发的存储介质,寓意每个工具可以被动态加载进运行时。

它的核心定位很明确:把每个 MCP 工具跑成一个独立的 WebAssembly 组件,由 WASI 接口统一管控工具的权限。

听起来有点绕,我们直接看架构图。上面那张图来自官方 README,左边是 AI Agent(Claude Desktop、Cursor、GitHub Copilot 等),它们通过 MCP 协议连接 Wassette 服务端,Wassette 再去调用各种 WebAssembly 组件。

注意一个关键点:Wassette 本身是一个普通的 Rust 原生进程,它跑在主机上,有完整的系统权限。真正被关进沙盒的是那些 WASM 组件——也就是每个具体的工具。

举个例子,当 AI Agent 调用”读取文件”这个工具时,实际上是这个流程:Wassette 收到请求 → 实例化对应的 WASM 组件 → WASM 组件在 Wasmtime 运行时里执行 → WASI 接口检查权限 → 返回结果。如果这个组件没有被授权访问某个目录,WASI 直接拒绝,连代码都碰不到那个路径。

这和容器化部署有本质区别。Docker 隔离的是一个完整操作系统,WASM 隔离的是一个函数级别的运行时。

值得注意的一点是:Wassette 目前还在早期开发阶段,官方文档也坦承「现有的 MCP server 代码需要重写才能跑在 WASM 上」,这不是一个平滑的迁移路径,而是一个范式转换。但正因为如此,它的安全收益和灵活性值得这个代价。


最小权限:每个工具各有一把钥匙

Wassette 最有价值的设计是 per-component 权限系统

一个 AI Agent 通常会有很多工具,每个工具的权限需求完全不同:

  • 获取当前时间:不需要任何权限
  • 抓取网页:只需要访问特定域名
  • 读取文件:只需要访问特定目录
  • 发送邮件:只需要 SMTP 端口

如果把这些权限绑在 Wassette 进程本身上,就只能选最宽松的(全开放)或者最严格的(全禁止)。但现实是每个工具的权限需求差异巨大。

Wassette 的解决方案是为每个 WASM 组件单独配置权限策略,写在 YAML 文件里:

version: "1.0"
permissions:
  network:
    allow:
      - host: "api.github.com"
      - host: "*.github.com"
  storage:
    allow:
      - uri: "fs:///workspace/project"
        access: ["read"]

上面这段配置的意思是:这个组件只能访问 GitHub 相关的域名,且只能读取 /workspace/project 目录。

更重要的是,这些权限不是靠代码里的 if 判断来”约束”的,而是由 WASI 接口在运行时强制执行。WASM 模块里的代码根本没有任何途径绕过这个限制——沙盒边界是语言运行时层面的,不存在”绕过”这个概念。

用一句话总结:Wassette 是主人(原生进程,权限完整),WASM 组件是工具(被沙盒关起来,只能持有一把特定的钥匙)。

Wassette 还支持通过 OCI Registry 分发组件,并附带加密签名验证。这意味着同一个 WASM 组件可以通过标准的容器镜像分发,签名可验证,版本可追踪。

Wassette 提供了 load-componentsearch-components 等内置 MCP 工具来管理组件的生命周期——搜索、加载、卸载,都不需要重启服务。组件通过 manifest 文件声明,Wassette 负责从 OCI Registry 拉取、校验、实例化。下面是一个简单的 manifest 配置:

version: 1
components:
  - uri: oci://ghcr.io/microsoft/get-weather-js:latest  # 从 GitHub Container Registry 拉取
    name: weather-service                                  # 给组件起个名字
    permissions:
      network:
        allow:
          - host: api.openweathermap.com                  # 只允许访问天气 API
      environment:
        allow:
          - key: OPENWEATHER_API_KEY                     # 只暴露这一个环境变量

生产环境还可以加上 digest 锁定具体版本,防止同名 tag 被篡改:

  - uri: oci://ghcr.io/microsoft/get-weather-js:1.2.3
    digest: sha256:1234567890abcdef...  # 锁定为特定哈希版本

WebAssembly 在 AI 工具赛道的天然优势

聊到这里,可以回过头来说说 WebAssembly 本身。

WASM 最初是为浏览器设计的指令集,让 C/C++/Rust 代码能在网页里以接近原生的速度运行。但它的技术特性——沙盒隔离、快速启动、跨平台——天然匹配 AI Agent 工具的场景。

第一,冷启动快,内存开销低。 传统容器化方案(Docker)的冷启动是秒级,WASM 是毫秒级。更重要的是,同等数量的工具实例,WASM 的内存占用远低于容器——官方明确把这个列为核心优势之一。

第二,沙盒是语言运行时级别的。 容器通过 Linux Namespace 和 Cgroup 实现隔离,WASM 的隔离发生在语言运行时层面。对于安全敏感的 AI 工具场景,这种更细粒度的隔离更有价值。

第三,WASI 接口提供统一的能力描述。 WASI[3](WebAssembly System Interface)是一套标准化的系统接口抽象,让 WASM 模块用统一的方式描述自己需要哪些权限、需要访问哪些资源。这意味着:同一个 WASM 组件,在任何支持 WASI 的运行时上都能以相同的权限模型运行。

这三个特性加在一起,对于 AI 工具场景来说几乎是量身定制的:工具需要被隔离执行、需要最小权限、需要在短时间内被实例化。


对比 MCP 规范:缺陷在哪里?

文章写到这里,有必要看一下标准 MCP 规范[4] 里对工具的定义。

MCP 的 Tool 结构在官方 schema 里是这样的(省略了部分字段):

interface Tool {
  namestring;
  description?: string;
  inputSchemaobject;
  outputSchema?: object;
  annotations?: {
    readOnlyHint?: boolean;
    destructiveHint?: boolean;
    idempotentHint?: boolean;
    openWorldHint?: boolean;
  };
}

发现了没有?annotations 里的字段全是 hints(提示),不是强制约束。 readOnlyHint: true 只是告诉 AI 这个工具不会修改环境,并没有在运行时真正阻止它修改。这个信息来自工具开发者的描述,可信度依赖开发者的自觉。

换句话说:MCP 规范只定义了”工具长什么样”,没有定义”工具被允许做什么”。

Wassette 的 per-component 权限系统恰好填补了这个空白。它不是在重新定义 MCP 协议,而是在 MCP Server 的实现层面,用 WASM + WASI 构建了一层真正的安全边界。


自定义工具:如何开发一个 Wassette 组件?

Wassette 的设计哲学是:开发者写的是工具函数,而不是服务器。 这是一个根本性的范式转变。

传统的 MCP 开发:你写一个服务器进程,定义几个 tools,进程启动后注册到 AI Agent。

Wassette 的开发方式:你写一个函数,编译成 WASM 组件,交给 Wassette 加载运行。下面用官方 examples 里的时间服务器为例,看一个完整开发流程。

第一步:定义 WIT 接口。 WASM Component 使用 WIT(WebAssembly Interface Types)来描述组件的能力:

package local:time-server;

interface time {
    /// Get the current date and time as a formatted string
    get-current-time: func() -> string;
}

world time-server {
    export time;
}

第二步:实现工具逻辑。 用 JavaScript 写核心功能,不需要关心协议:

// time.js
export const time = {
    getCurrentTimeasync () => {
        return new Date().toISOString();
    }
};

第三步:配置权限策略。 时间工具不需要任何系统权限,所以 policy 是空的:

version: "1.0"
description: "Permission policy for time-server"
permissions: {}  # 时间工具不需要任何权限

对比一下文件系统工具的 policy,就知道权限配置有多细:

version: "1.0"
permissions:
  storage:
    allow:
      - uri: "fs:///home/user/project"
        access: ["read"]           # 只读访问项目目录
      - uri: "fs:///tmp"
        access: ["read""write"]  # 可读写临时目录

第四步:编译成 WASM 组件。 用 wasm-bindgen/wit-bindgen 工具链编译,产出 .wasm 文件。

第五步:加载到 Wassette。 通过 Wassette 内置的 MCP 工具 load-component 把编译好的 .wasm 文件加载进去,指定对应的 policy 文件。

整个过程中,工具代码不知道自己运行在沙盒里,也不需要主动做安全检查——权限边界由 WASI 接口在底层强制执行,开发者无感知。


总结

AI Agent 的工具安全问题目前还没有被广泛重视,但它是一个真实存在的风险敞口。当一个 AI 助手有能力调用十几个不同来源的工具时,如果没有任何权限隔离机制,某个被恶意污染的工具就能以 Agent 的完整权限访问系统资源。

WebAssembly 之所以适合这个场景,不是因为它有多酷炫,而是因为它的三个技术特性——沙盒隔离、快速启动、WASI 标准化——刚好解决了 AI 工具安全运行最核心的几个问题。

Wassette[5] 是一个有价值的参考实现:用 Rust 构建 MCP Server 端,用 Wasmtime 运行 WASM 组件,用 WASI 做权限拦截。它的 per-component 权限模型把”最小权限原则”真正落到了运行时层面,而不是停留在代码注释和文档里。

当一个技术方向正确的时候,往往会有多个项目同时朝它走。 除了 Wassette,Mozilla 的 WASM Agents Blueprint、LangChain 的 WASM 集成、WasmEdge 的 AI 推理支持,都在从不同角度推进 WebAssembly 在 AI 场景的落地。这个方向,值得持续关注。

引用链接

[1]WebAssembly: https://webassembly.org

[2]Wassette: https://github.com/microsoft/wassette

[3]WASI: https://github.com/WebAssembly/WASI

[4]MCP 规范: https://modelcontextprotocol.io

[5]Wassette: https://github.com/microsoft/wassette