Lion Cipher Lab
Sandbox untrusted code with WebAssembly and WASI (build the plugin host, prove the boundary)
- Cyber / Security Engineer
- Advanced
- ~90 min
- Free · no account required
Why it matters
Sooner or later a real system has to run code it does not trust: a customer-supplied plugin, a marketplace extension, a user-submitted function at the edge, one tenant's job on hardware shared with another tenant's. The old answers are heavy and leaky. A whole container or virtual machine per untrusted snippet is slow to start and large to run, and it still shares a kernel whose entire syscall surface is now attack surface. A language-level sandbox -- a restricted interpreter, a `seccomp` filter bolted onto a process -- is a deny-list you have to get exhaustively right, and the history of…
What you'll learn
- Writing small WebAssembly modules by hand in the WebAssembly text format (.wat) -- a pure compute function, a function that imports a host capability, and a function that deliberately misbehaves -- with no C or Rust toolchain
- Building a plugin host in Python with the wasmtime runtime that instantiates an untrusted module with ZERO ambient authority, and understanding why WebAssembly has no syscalls of its own
- Proving deny-by-default: an untrusted module that reaches for a capability the host never granted cannot even start (unknown import), and an empty WasiConfig denies every file, env var and socket
- Granting exactly one capability explicitly -- a single host function, or one preopened WASI directory -- and proving the module now reaches THAT and only that, with the '../' escape still denied by WASI's path-capability model
- Reading the real WASI errno codes a capability boundary returns: EBADF when no directory was granted, SUCCESS inside the grant, EPERM on an escape attempt, ENOENT for a missing file inside
- Capping resource consumption so a malicious module cannot deny service to the host: a StoreLimits memory cap that refuses unbounded growth, and a fuel budget that traps an infinite loop with OUT_OF_FUEL
- Explaining WHY a buffer overflow inside a wasm module corrupts only that module's linear memory and can never reach host memory -- the fault-isolation win -- and naming what WASM does NOT cover (side channels, careless imports)
- Judging when WebAssembly sandboxing is the right tool (untrusted plugins, edge functions, multi-tenant compute) versus a container, gVisor/seccomp, or a language sandbox
Steps in this lab
- The idea: code with no powers, and one window you open on purpose
- Set up: install the runtime, make one file
- Write the untrusted modules, by hand, in the WebAssembly text format
- Act 1 -- run untrusted compute with an EMPTY sandbox, and contain an overflow
- Act 2 -- deny by default: reach for a capability nobody granted, then grant exactly one
- Act 3 -- grant ONE directory with WASI, and prove the '../' escape is denied
- Act 4 -- the other half of sandboxing: stop a runaway from taking the host down
- Run it: read the whole boundary in one pass of real output
- The judgment: when to reach for wasm, and how to do it better
- Honest limits: what this sandbox does NOT protect you from