A wasm-only coding agent: file tools plus exactly one execution tool,
wasmtime_exec. There is no shell — the only way to observe program
behaviour is to write Rust and compile to WebAssembly.
Execution is super fast because there is no external process.
wasmtime_exec embeds the wasmtime/wasmtime-wasi crates directly.
- No ambient authority. By default a compiled guest only gets WASI
stdio (
println!/eprintln!, captured into in-memory pipes, not inherited from the host process) andargv. No filesystem, no network, no clock/random beyond what WASI exposes, unless the agent explicitly asks for it on that call. - Filesystem access is per-call and explicit.
wasmtime_exec'sdirs=preopens exactly the host directories named, at the guest paths named (src/tools/wasm_exec.rs). Nothing outside those directories is reachable — there's no general filesystem root. - Network access is host-bounded. This
embeds a custom host stdlib
(
wasm_agent_host:http_request/http_read,src/host_stdlib.rs) exposing whitelist-gatedhttp_get/http_postto the guest (assets/wasm_agent_std.rs). The whitelist is enforced twice: the import module is only linked into theLinkerwhen--http-allowis non-empty, so a module built without a whitelist configured fails to instantiate (unresolved import); and every request's host is checked against the allowlist before any HTTP call is made, so a request to a non-whitelisted host never reaches the network (-2, zero requests — see the tests insrc/tools/wasm_exec.rs). - Deterministic execution bounds. Instead of a wall-clock timeout,
wasmtime_execruns with fuel metering (Config::consume_fuel,store.set_fuel, default 20,000,000,000 instructions,--fuel/$WASM_AGENT_FUEL). An exhausted budget traps at an instruction boundary — the same clean trap as any other fault, nothing half-applied, nothing left running.
The build step is not sandboxed — this is the biggest misalignment
risk in this design. compile_rust_to_wasm runs rustc directly on
the host (src/tools/rustc.rs): a plain Command::new(rustc), same
user, same filesystem, same environment as the wasm-agent process
itself, bounded only by a 300s wall-clock timeout — no fuel, no WASI,
none of the guarantees described above. Everything in Sandboxing
applies to the compiled .wasm, not to the compiler that produces it.
Concretely, at compile time the agent-authored Rust source can:
- read arbitrary host files/env vars via
include!,include_str!,env!,option_env!, etc., and have their contents show up in compiler diagnostics or get baked into the.wasmbinary — a way to leak host information into the sandbox loop that bypassesdirs=and--http-allowentirely, since those only gate the guest, not the compiler; - in principle, run arbitrary unsandboxed code at compile time via
build.rsbuild scripts or proc-macro crates, which execute as part of compilation, not inside wasmtime. This build currently has nocargo/build.rspath at all —compile_rust_to_wasm's optionaldeps=only adds-L dependency=...search paths for source pulled in via#[path](following the originalwasm_agent.py's "no cargo, no build scripts" approach), and there's nocargo_installtool in this build to vendor a crate in the first place — but that's a property of what's wired up today, not a guarantee enforced by the sandbox. Adding a Cargo-driven build path later would reopen this.
None of this is mitigated by wasmtime, fuel, or the HTTP whitelist —
those only constrain the guest module. Treat rustc invocations the
way you'd treat any other untrusted-input-to-a-trusted-compiler
pipeline: it currently runs with the same privileges as the agent
process, on whatever host it runs on.
This is a Rust rewrite and extension of wasm_agent.py
There is more info at https://www.monperrus.net/martin/wasm-agent.
cargo build --release
cargo run -- "write a Rust program, compile it to wasm, and run it"
cargo run -- --demo # self-contained demo, no API key
cargo run -- --demo --http-allow example.com # demo including the http_get step
The model defaults to glm-5.2 on the z.ai coding endpoint. The API key
is resolved from --api-key, $WASM_AGENT_API_KEY, or the OS keyring
(service z.ai, username api_key).
Pass --http-allow domain[,domain...] to let the compiled guest module
call http_get/http_post (see assets/wasm_agent_std.rs) against
those domains; without it the guest has no network access at all.