JosephTLucas/wasm_af
GitHub: JosephTLucas/wasm_af
一个基于 WebAssembly 组件模型和 OPA 策略引擎构建的安全优先型 AI 代理编排框架,通过结构化沙箱实现零信任执行。
Stars: 7 | Forks: 1
# WASM_AF — WebAssembly Agent Framework
**一个安全优先的 AI 代理编排框架,基于 [WebAssembly Component Model](https://component-model.bytecodealliance.org/) 和 [wasmtime](https://wasmtime.dev/) 构建。**
WASM_AF 利用 WebAssembly 的沙箱化和 ephemeral(随用随弃)特性,创建了一个零信任的 AI 代理运行时。代理是 WASM 组件,默认被隔离,依据策略获得能力,并在任务完成后销毁。

## 快速开始
```
export LLM_API_KEY="your-key-here"
export LLM_BASE_URL="https://api.openai.com/v1"
cd examples/wasmclaw
LLM_MODE=api make reply-all-demo
```
前置条件:[Rust](https://rustup.rs/)(包含 `wasm32-wasip2` 目标)、[NATS server](https://nats.io/)(或自带 NATS 的 [wash](https://wasmcloud.com/docs/installation/))、[jq](https://jqlang.github.io/jq/)。
对于 API 推理,请在 repo 根目录下的 `.env` 文件(已 gitignore)中设置 `LLM_API_KEY` 和 `LLM_BASE_URL`,或者在 shell 中 export 它们。
## 为什么选择 WASM + AI Agents?
大多数代理框架通过**约定**来强制安全性:仔细配置你的工具,不向不需要的代理传递凭据,通过应用层检查限制网络访问。而这个 WebAssembly 框架通过**结构构建**来强制安全性。WASM 组件**无法**接触文件系统、网络或环境,除非由编排器显式授予访问权限。
## 架构
```
sequenceDiagram
participant Client
participant Orchestrator
participant OPA
participant NATS
participant Agent as Agent (WASM)
Client->>Orchestrator: POST /message (or /tasks)
Orchestrator->>Orchestrator: build DAG of steps
Orchestrator->>NATS: persist task state
loop for each ready step (parallel)
Orchestrator->>Orchestrator: compute context_taint from ancestor outputs
Orchestrator->>OPA: evaluate step (capabilities + context_taint)
OPA-->>Orchestrator: allowed host functions, limits, approval
Orchestrator->>Agent: instantiate with selective Linker
Agent-->>Orchestrator: execute() result
Note right of Agent: instance dropped immediately
Orchestrator->>Orchestrator: store result + propagate taint labels
Orchestrator->>NATS: write result + audit entry
end
Orchestrator-->>Client: task result
```
编排器是一个嵌入 [wasmtime](https://wasmtime.dev/) Component Model 运行时的单一 Rust 二进制文件。对于每个任务步骤,它会组合一个仅包含 OPA 许可的 host 接口的 `Linker`,实例化代理组件,调用其 `execute` 导出函数,读取结果,然后 drop 该实例。
**结构性的能力缺失**:如果 OPA 不允许某一步骤使用 `host-exec`,该接口就不会被添加到 Linker 中。组件将无法实例化 —— 该能力在结构上是不存在的,而不仅仅是在运行时被拒绝。
NATS JetStream KV 提供任务状态持久化和不可变的审计追踪。
## 核心原则
**策略驱动的能力授予。** [Open Policy Agent (OPA)](https://www.openpolicyagent.org/) 评估能力授予并控制执行的每一步。来自 Rego 的结构化决策(`allowed_hosts`、`max_memory_pages`、`timeout_sec`、`host_functions`、`config`、`allowed_paths`、`requires_approval`)控制哪些 WIT 接口被链接到每个组件实例。策略输入包含 `context_taint` —— 来自祖先步骤的来源标签集 —— 因此 Rego 规则可以基于数据来源拒绝执行或要求审批。默认拒绝;如果没有 `OPA_POLICY`,编排器将无法启动。
**实例级作用域。** 每个组件都有自己的 `Linker` 和 `Store`。当多个 url-fetch 实例并行运行时,每个实例都仅限于一个特定域。
**无代理间通信。** 代理之间互不通信。编排器调解所有数据流,将中间结果存储在 NATS KV 中,并将上下文从祖先步骤传递给其依赖项。
**WIT 类型的 host 接口。** LLM 推理、shell 执行、邮件发送和 KV 存储都是 WIT 接口,仅链接到需要它们的组件。一个不导入 `host-llm` 的组件无法调用它 —— 该导入在其 world 中不存在。凭据(API keys, SMTP)存在于 Rust 闭包中;它们永远不会进入 WASM 内存。
**Ephemeral 生命周期。** 每个组件在单个函数作用域内经历 实例化 → `call_execute` → 销毁。一个不存在代理或运行时是无法被利用的。底层编译好的 `Component` 在首次加载后会在内存中缓存,因此后续调用的实例化成本接近于零 —— 磁盘 I/O 和 JIT 编译在每个进程的生命周期内只发生一次。
**自带代理。** 外部 WASM 组件可以通过 `POST /agents` 上传到运行中的编排器。它们会根据 WIT world 定义进行验证,存储在磁盘上,并以 `capability: "untrusted"` 注册。BYOA Rego 策略层(`policies/byoa.rego`)应用严格的沙箱默认值 —— 无 host 函数、无网络、4 MiB 内存、10 秒超时、强制审批。
**人机交互审批关卡。** 当 OPA 策略对某一步骤返回 `requires_approval: true` 时,编排器会暂停该步骤而不是执行它。DAG 的其他分支继续运行。只有在通过 HTTP API 显式 approve 或 reject 后,执行才会恢复。
```
# 在你的 policy.rego 中 — 批准是按 agent 类型 opt-in 的:
requires_approval if { input.step.agent_type == "email-send" }
approval_reason := "email delivery requires human approval" if { input.step.agent_type == "email-send" }
```
**污点追踪。** 数据来源标签(`web`、`external`、`untrusted`)随步骤输出一起在 DAG 中传播。当像 `url-fetch` 或 `web-search` 这样的代理从外部源生成数据时,该输出会被标记上污点。该污点会流向每一个消费该数据的下游步骤。OPA 策略可以根据步骤上下文中存在哪些污点标签,拒绝执行、要求审批或应用其他控制。受信任的平台代理可以*解密*污点 —— 一个通过 LLM 处理原始 web 内容的 `summarizer` 会从其输出中剥离 `web` 标签,因此下游步骤看到的是干净的数据。BYOA 代理始终被标记为 `untrusted` 污点,并且永远无法解密污点。
```
# 阻止 web 来源的数据到达 shell 执行:
allow if {
input.step.agent_type == "shell"
not _context_has_web_taint
...
}
# 当 web 污染数据流向 LLM 时要求批准:
requires_approval if {
"web" in input.context_taint
"llm_complete" in input.agent.host_functions
not _is_declassifier
}
```
## API
### 健康检查
| Method | Path | Description |
|---|---|---|
| `GET` | `/healthz` | 当编排器就绪时返回 `200 OK` |
### 任务生命周期
| Method | Path | Description |
|---|---|---|
| `POST` | `/message` | 同步聊天端点 (提交 + 轮询 + 返回响应) |
| `POST` | `/tasks` | 提交新任务 (返回 `task_id`) |
| `GET` | `/tasks/{id}` | 获取任务状态 (计划、步骤状态、结果) |
### 审批关卡
| Method | Path | Description |
|---|---|---|
| `GET` | `/tasks/{id}/approvals` | 列出等待审批的步骤 |
| `POST` | `/tasks/{id}/steps/{stepId}/approve` | 批准一个步骤 (body: `{"approved_by": "alice"}`) |
| `POST` | `/tasks/{id}/steps/{stepId}/reject` | 拒绝一个步骤 (body: `{"rejected_by": "bob", "reason": "..."}`) |
### 自带代理 (BYOA)
| Method | Path | Description |
|---|---|---|
| `POST` | `/agents` | 上传 `.wasm` 组件 + 元数据 (multipart form) |
| `DELETE` | `/agents/{name}` | 移除外部代理 (平台代理受保护) |
| `GET` | `/agents` | 列出所有代理 (包含 `external` 标志) |
外部代理在运行时注册,并自动获得 `capability: "untrusted"` 且无任何 host 函数。
```
curl -X POST localhost:8080/agents \
-F 'meta={"name":"my-agent","context_key":"my_agent_result"}' \
-F 'wasm=@my_agent.wasm'
curl localhost:8080/agents | jq .
curl -X DELETE localhost:8080/agents/my-agent
```
完整指南请参见 [docs/creating-an-agent.md](docs/creating-an-agent.md)。
## 项目结构
```
wasm_af/
├── Cargo.toml # Rust workspace (orchestrator crates)
├── Makefile # build, test, demo
│
├── wit/
│ └── agent.wit # WIT interface definitions (the agent contract)
│
├── docs/
│ └── creating-an-agent.md # guide: platform agents + BYOA walkthrough
│
├── crates/
│ ├── orchestrator/ # the framework — Rust binary (axum + wasmtime)
│ │ ├── src/
│ │ │ ├── main.rs # HTTP server, env config, startup
│ │ │ ├── engine.rs # wasmtime Component loading, selective Linker
│ │ │ ├── host/mod.rs # WIT Host trait impls (llm, kv, exec, etc.)
│ │ │ ├── policy.rs # OPA evaluator (regorus, evaluates Rego per step)
│ │ │ ├── scheduler.rs # DAG scheduler, parallel dispatch, splice
│ │ │ ├── registry.rs # agent registry (thread-safe, mutable at runtime)
│ │ │ └── api.rs # HTTP handlers (message, submit, get, approve, reject, BYOA)
│ │ └── Cargo.toml
│ │
│ ├── dag/ # DAG: dependency graph, ready-set, ancestors, splice
│ │ └── src/lib.rs
│ │
│ └── taskstate/ # NATS JetStream KV: task state, audit log, payloads
│ └── src/
│ ├── lib.rs
│ ├── store.rs # KV store implementation
│ └── types.rs # shared types (Status, TaskState, etc.)
│
├── policies/ # reusable OPA policy modules
│ └── byoa.rego # untrusted-agent sandbox tier
│
├── runtimes/ # WASI sandbox runtimes (downloaded, not checked in)
│ ├── build.sh # downloads Python WASM (SHA256-verified)
│ └── python.wasm # CPython 3.12 for wasm32-wasi (gitignored)
│
├── components/ # Rust workspace — WASM agent components (wit-bindgen)
│ ├── .cargo/config.toml # default target: wasm32-wasip2
│ └── agents/
│ ├── router/ # LLM-based skill router (classifies → skill + params)
│ ├── shell/ # host command execution via host-exec
│ ├── sandbox-exec/ # sandboxed code execution via host-sandbox
│ ├── file-ops/ # WASI std::fs (no host functions)
│ ├── email-send/ # host fn email delivery via host-email
│ ├── email-read/ # config-injected inbox reader via host-config
│ ├── memory/ # conversation history via host-kv
│ ├── responder/ # LLM response generation via host-llm
│ ├── url-fetch/ # URL fetching via wasi:http
│ ├── web-search/ # Brave Search API via wasi:http
│ └── summarizer/ # LLM summarization via host-llm
│
└── examples/
├── pii-pipeline/ # BYOA demo: Python agent in a multi-agent pipeline
├── prompt-injection/ # security demo: injection fails structurally
└── wasmclaw/ # personal AI assistant with two-tier execution
├── lib/setup.sh # shared infra: build, NATS, orchestrator, cleanup
├── run.sh # main demo (skills, security, approval, taint tracking)
├── reply-all-demo.sh # parallel DAG demo (jailbreak + approval)
├── taint-demo.sh # taint tracking demo (deny, approval gate, declassification)
├── agents.json # agent registry (includes output_taint, declassifies)
├── policy.rego # step policy (capability gates + taint-aware rules)
├── submit.rego # submission policy
├── jailbreak.rego # prompt injection / jailbreak detection rules
├── data.json # allowlists, feature flags, taint gates, jailbreak patterns
├── *_test.rego # 92 OPA tests (opa test .)
└── Makefile
```
## 运行演示
### Wasmclaw (个人 AI 助理)
```
cd examples/wasmclaw
make demo # mock LLM (deterministic, no deps)
LLM_MODE=api make demo # OpenAI-compatible API (needs LLM_API_KEY + LLM_BASE_URL)
make reply-all-demo # parallel DAG: jailbreak + approval (interactive Y/n)
make taint-demo # taint tracking: deny, approval gate, declassification
# (needs LLM_API_KEY + LLM_BASE_URL + BRAVE_API_KEY in .env)
```
### PII Pipeline (自带代理)
```
cd examples/pii-pipeline
make demo # mock LLM (deterministic, no deps)
LLM_MODE=api make demo # OpenAI-compatible API (needs LLM_API_KEY + LLM_BASE_URL)
```
### Prompt Injection
```
cd examples/prompt-injection && make demo # requires Ollama (pulls model automatically)
```
## 配置
### Orchestrator
| Variable | Default | Description |
|---|---|---|
| `LISTEN_ADDR` | `0.0.0.0:8080` | HTTP server 监听地址 (接受 `:PORT` 或 `HOST:PORT`) |
| `WASM_DIR` | `./components/target/wasm32-wasip2/release` | 包含已编译 `.wasm` 组件的目录 |
| `NATS_URL` | `nats://127.0.0.1:4222` | NATS server 地址 |
| `OPA_POLICY` | — | `.rego` 文件或目录的路径 (**必需**) |
| `OPA_DATA` | — | JSON 数据文件的路径 (在启动时填充 OPA 数据存储) |
| `AGENT_REGISTRY_FILE` | — | JSON 代理注册表文件的路径 (必需) |
| `AGENT_REGISTRY` | — | 内联 JSON 代理注册表 (优先于文件) |
| `LLM_MODE` | `mock` | `mock` 用于确定性路由,`api` 用于远程推理 (任何 OpenAI 兼容端点),`real` 用于本地 Ollama |
| `LLM_BASE_URL` | — | OpenAI 兼容的 API base URL |
| `LLM_API_KEY` | — | LLM 端点的 API key (`LLM_MODE=api` 时必需) |
| `LLM_MODEL` | `gpt-4o-mini` | LLM 端点的模型名称 |
| `LLM_TEMPERATURE` | — | 默认采样温度 |
| `LLM_TIMEOUT_SEC` | `120` | LLM API 调用的 HTTP 客户端超时时间 |
| `PLUGIN_TIMEOUT_SEC` | `30` | 每次组件调用的最大挂钟时间(秒) |
| `PLUGIN_MAX_MEMORY_PAGES` | `256` | 每个组件的最大 WASM 内存页数 (每页 64 KiB) |
| `SHELL_ALLOWED_COMMANDS` | `ls,cat,pwd,...` | 逗号分隔的命令二进制白名单 |
| `SHELL_ALLOWED_PATHS` | `/tmp/wasmclaw` | 用于 shell 参数限制的逗号分隔路径基前缀 |
| `SHELL_TIMEOUT_SEC` | `10` | 每次 shell 命令执行的最大挂钟时间(秒) |
| `SANDBOX_RUNTIMES_DIR` | `./runtimes` | 包含 WASI 运行时 `.wasm` 文件的目录 |
| `SANDBOX_TIMEOUT_SEC` | `30` | 每次沙箱代码执行的最大挂钟时间(秒) |
| `SANDBOX_ALLOWED_LANGUAGES` | `python` | 用于 sandbox-exec 的逗号分隔语言白名单 |
| `SANDBOX_ALLOWED_PATHS` | `/tmp/wasmclaw` | 挂载到沙箱实例的逗号分隔 host 路径 |
| `EMAIL_ALLOWED_DOMAINS` | `example.com,partner-corp.com` | 逗号分隔的收件人域白名单 |
| `APPROVAL_WEBHOOK_URL` | — | 用于 POST 审批事件的 URL |
| `APPROVAL_TIMEOUT_SEC` | `0` | N 秒后自动拒绝步骤 (0 = 无超时) |
### API Mode
当 `LLM_MODE=api` 时,编排器连接到任何 OpenAI 兼容的推理端点。在你的环境或 `.env` 文件中设置 `LLM_BASE_URL` 和 `LLM_API_KEY`。端点上可用的任何模型都可以通过 `LLM_MODEL` 选择。
## WIT 接口定义
代理契约定义在 `wit/agent.wit` 中。每个代理组件实现 `execute` 导出,并可以导入任何 host 接口的子集:
| Interface | Functions | Purpose |
|---|---|---|
| `host-llm` | `llm-complete` | LLM 推理 (凭据在 host 闭包中) |
| `host-kv` | `kv-get`, `kv-put` | NATS JetStream KV (按代理类型划分命名空间) |
| `host-exec` | `exec-command` | Host 命令执行 (白名单限制) |
| `host-sandbox` | `sandbox-exec` | 沙箱代码执行 (WASI 运行时) |
| `host-email` | `send-email` | 邮件发送 (域名白名单) |
| `host-config` | `get-config` | 只读配置 (始终可用) |
代理仅导入它们需要的接口。编排器的 `Linker` 仅提供 OPA 允许的内容。不匹配 = 实例化失败 = 结构性拒绝。
共享的 `kv-pair` 类型带有一个可选的 `taint` 字段。编排器使用从 DAG 计算出的来源标签填充它;代理可以检查它以做出污点感知的决策(例如,注释 LLM prompts),或者完全忽略它。
```
record kv-pair {
key: string,
val: string,
taint: option
- >,
}
```
## 许可证
Apache License 2.0 — 详见 [LICENSE](LICENSE)。
标签:AI安全, AI工具, Chat Copilot, FTP漏洞扫描, Lerna, NATS, OPA, Rust, Wasmtime, WebAssembly, XSS注入, 人工智能体, 可视化界面, 安全编排, 微服务安全, 沙箱隔离, 策略执行, 组件模型, 结构化提示词, 网络安全审计, 网络流量审计, 能力访问控制, 运行时防护, 通知系统, 零信任架构, 靶场