Path B — Managed WASM hosting¶
You let Lattice run your authoritative server for you, on Lattice infrastructure, next to other
tenants. Because the platform now runs your code on its machines, the module is untrusted
code — so it runs inside a WebAssembly capability sandbox. You compile the same game-sim
source to wasm32, upload it, and it is import-scanned, admitted, and scheduled. This path is the
full 09 — WASM Managed Hosting, distilled.
Prerequisites
Finish the common setup first. Tooling: clang + lld able to target
wasm32 (apt-get install clang lld).
1. Safety comes from the sandbox, not from scanning¶
The single most important idea:
A wasm module has its own linear memory, no ambient authority (no syscalls, files, sockets, or host pointers), and can affect the outside world only through the imports the host explicitly grants it. Grant nothing but the fixed
lattice_ms_*host functions, and — by construction — the module can do nothing else.
The admission scan is therefore not a malware detector; it is a static proof that the sandbox boundary is the only boundary — it confirms the module imports nothing outside the granted capability set.
2. Author against the managed-sim surface¶
For managed hosting your sim is authored against lattice_managed.h — a WASM-crossable projection
of the full ABI (see bindings → managed-sim surface). It is a faithful subset: state lives in the module's
memory; everything crosses as scalars or a (ptr, len) byte blob; host→module events are pulled
each tick. The same source still builds native (it links the lattice_ms_* shim over a real
lattice_runner).
3. Compile to wasm32¶
clang --target=wasm32 -nostdlib -Wl,--no-entry -Wl,--allow-undefined \
-O2 -Iinclude -o game_sim.wasm game_sim.cpp
--allow-undefined leaves the lattice_ms_* calls undefined; the linker emits them as
env.lattice_ms_* function imports. The managed host is the only thing that satisfies them.
(reference/host/lattice-wasm-host/build.sh does this dual-compile for you.)
4. Upload → scan → register → schedule¶
Upload to the console (RBAC: only the game owner / super-admin):
WASM_B64=$(base64 -w0 game_sim.wasm)
curl -s -X POST http://localhost:3004/games/$GAME/modules \
-H "authorization: Bearer $DEV_TOKEN" -H 'content-type: application/json' \
-d "{\"Packaging\":\"wasm\",\"Version\":\"1.0.0\",\"WasmBase64\":\"$WASM_B64\"}" | jq
# admitted => { "Admitted": true, "Schedulable": true, "Imports": ["lattice_ms_log", ...], "Sha256": "..." }
flowchart TD
U["POST /games/{id}/modules (wasm)"] --> S{import-allowlist scan<br/>WasmImportScanner}
S -->|"import ∉ allowlist / oversized"| REJ["400 — reasons named<br/>NOTHING stored"]
S -->|admitted| REC["ModuleBuild stored<br/>SHA-256 + imports + Schedulable=true"]
REC --> SCH["director schedules it under the wasm tier"]
SCH --> RUN["WASM sandbox runs your sim<br/>grants ONLY env.lattice_ms_*"]
A tampered module that imports env.system / env.fd_write is rejected with the offending
imports named, and is never stored or made schedulable.
5. Egress is host-controlled¶
The one capability that can touch the outside world is the mediated web
fetch (lattice_ms_http_request → lattice_http_request):
- the egress allow-list (
host:port) is configured by the host, not the module — the module cannot widen its own egress; - empty allow-list ⇒ deny all (egress is opt-in);
- a non-allow-listed request is rejected immediately (
LATTICE_HTTP_NOT_ALLOWED); an allow-listed one is admitted, then rate-limited.
This is the SSRF / exfiltration defence — the server cannot be turned into an arbitrary outbound proxy.
6. Native isolation tier (alternative)¶
Some modules need native performance/threads the wasm subset doesn't offer. The alternative isolation tier for untrusted native modules is a microVM (Firecracker-style) or a hardened container — one tenant per VM, a mediated syscall surface, and the same egress allow-list at the VM's network boundary. This is a different boundary from the import scan; ELF/PE inspection is defence-in-depth only, never the isolation boundary. See 09 §8.
The wasm module you uploaded was compiled from the same source as the native binary in Path A — and it produces a bit-identical simulation. That equivalence is the point: write-once, dual-target.