Skip to content

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_requestlattice_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.