Use WebAssembly when code can target a supported guest interface and needs a small, explicit set of host capabilities. Use a hardened container or stronger virtual-machine boundary when code needs an ordinary Linux process, native packages, or broader language compatibility. Neither is secure by name alone: deny network and host mounts by default, run without privilege, cap every resource, minimize host calls, and destroy the environment after one task.
This guide provides a decision and verification method, not benchmark results. Startup and throughput depend on runtime, workload, compilation, caching, kernel, architecture, and policy. Measure your exact deployment before making performance claims.
Begin with the attack objective
Assume generated code is malicious. It will try to:
- read host, tenant, or previous-task files;
- steal environment variables and credentials;
- contact metadata services or attacker infrastructure;
- exploit the runtime or kernel;
- fork, allocate, write, or run forever;
- abuse permitted host functions;
- forge terminal or log output; and
- persist into caches or artifacts used by future tasks.
The sandbox security objective is containment, not code correctness:
One task may consume only its assigned input and budget, invoke only declared
capabilities, produce bounded inert output, and leave no durable state or
authority for another task.
Compare isolation models
| Requirement | Hardened container | WebAssembly runtime |
|---|---|---|
| Existing Linux binaries | Strong fit | Usually requires recompilation or adaptation |
| Native package ecosystem | Broad | Limited to supported guest interfaces |
| Syscall surface | Filtered Linux syscalls | No raw syscall access from core Wasm |
| Filesystem | Mount namespace and policy | Explicit WASI capabilities/preopens |
| Network | Network namespace and egress policy | Only linked host/WASI interfaces |
| Kernel exposure | Shares host kernel unless VM-backed | Runtime/JIT plus host-call attack surface |
| Portability | Image and architecture dependent | Portable within supported Wasm/WASI profile |
OCI's Linux runtime configuration uses namespaces, cgroups, Linux capabilities, security modules, and filesystem controls; omitting a namespace can cause the container to inherit the runtime namespace of that type (OCI Linux runtime configuration). Review generated configuration rather than assuming the platform's defaults.
Wasmtime documents core WebAssembly's bounds-checked linear memory, type-checked control flow, and rule that outside interaction occurs only through imports and exports. Its WASI filesystem follows a capability model: a guest can access only what the host grants (Wasmtime security). A runtime vulnerability or unsafe host function can still break the boundary.
Harden a container runner
Run a fresh sandbox per task with:
- a non-root UID and GID mapped through a user namespace where supported;
- a read-only root filesystem;
- an empty writable temporary filesystem with a size limit;
- only the task input mounted read-only and output mounted write-only where feasible;
- no host socket, container-runtime socket, device, home directory, or credential mount;
- a new PID, mount, IPC, UTS, cgroup, user, and network namespace;
- all Linux capabilities dropped unless one is proven necessary;
no_new_privs, a restrictive seccomp policy, and an enforced LSM profile;- no network interface or an egress proxy allowlisting exact destinations;
- CPU, memory, process, file-size, open-file, I/O, and wall-clock limits; and
- a minimal immutable image selected by digest.
Seccomp reduces available system calls; the Linux kernel documentation warns that seccomp is not a sandbox by itself and should be part of a larger hardening system (Linux seccomp filter documentation).
Do not run an untrusted container in the same privileged pod or task as the agent orchestrator. Sidecars and shared volumes can quietly defeat isolation. For higher-risk multi-tenant workloads, add a VM-backed container runtime or dedicated worker boundary based on your threat model.
Build a minimal WebAssembly capability surface
Start with no imports, then add only task-specific functions:
input.read(handle, offset, length)
output.write(handle, bytes)
clock.monotonic_now()
Avoid generic host functions such as fetch(url, headers, body), open(path), or exec(command). A narrow read_assigned_input is easier to authorize than a preopened parent directory.
Configure:
- memory maximums and table limits;
- execution fuel or epoch interruption plus a wall deadline;
- bounded host-call count and payload size;
- no filesystem preopens by default;
- no network interface unless required;
- a fresh store or instance per task;
- controlled stdout and stderr; and
- validation of every pointer, length, enum, and handle crossing the host boundary.
Core Wasm isolation does not make imported functions safe. If a host function accepts an arbitrary URL or filesystem path, it reintroduces SSRF or path traversal with the host's authority.
Wasmtime also notes that terminal escape sequences from untrusted output can affect or mislead terminals. Treat stdout as untrusted bytes, strip control sequences for display, and store raw output only in a restricted forensic channel.
Control supply chain and compilation
Pin container images by digest and Wasm modules by content hash. Verify signatures under an organizational trust policy, scan dependencies, and preserve the build provenance needed to reproduce the artifact.
Do not compile untrusted source in a more privileged environment than the final runtime. Compilers and package installers process attacker-controlled files and may execute build scripts. Either compile inside an equally constrained sandbox with no credentials and restricted network, or accept only prebuilt verified modules for high-risk paths.
Separate dependency download from execution. Maintain an allowlisted, scanned mirror rather than giving generated package names unrestricted internet access. A lockfile reduces drift but does not make packages trustworthy.
Make input and output one-way
Create a task directory with random internal identity and no user-controlled path components. Copy or stream only authorized files. Reject symlinks, device files, sockets, oversized archives, absolute paths, and traversal during extraction.
Return only declared artifacts. Validate media type, filename, byte size, count, structure, and content before moving them out of quarantine. Never automatically execute generated test reports, render unsanitized HTML, or open office documents in a privileged desktop session.
Destroy writable layers and cryptographic task keys after collection. Do not reuse language caches across tenants unless their contents are verified and namespaced; caches are a persistence channel.
Design network access as a brokered capability
The safest default is no network. When the task genuinely needs dependencies or an API, route through a broker that:
- accepts an outcome-specific request rather than an arbitrary socket;
- resolves and pins allowed destinations;
- blocks private, loopback, link-local, metadata, and internal ranges;
- removes user-supplied authorization headers;
- caps redirects, bytes, duration, and response types; and
- logs destination and policy decision without secrets.
DNS and redirects must be checked at connection time, not only when the URL is first parsed. See the agent SSRF guide for the complete flow.
Benchmark honestly
Measure cold compilation, cached startup, execution time, peak memory, artifact I/O, concurrent saturation, timeout accuracy, and cleanup. Use identical work and security policies where comparison is possible. Report failures and policy differences; a container running a full compiler and a precompiled Wasm function are not equivalent workloads.
Publish runtime and kernel versions, hardware, module/image hashes, policy configuration, raw samples, and date. Do not extrapolate one microbenchmark to security or total task latency.
Escape and exhaustion tests
- Read
/proc, host files, environment, and neighboring task paths. - Reach loopback, private networks, link-local metadata, DNS, and the public internet.
- Fork or create threads/processes until denied.
- Allocate memory, disk, descriptors, and output beyond every limit.
- Run infinite loops and blocking host calls.
- Create symlinks and malicious archives in output.
- Emit terminal control sequences and deceptive logs.
- Exploit an intentionally unsafe host import.
- Reuse caches or artifacts across two tenants.
- Kill the orchestrator mid-task and verify cleanup.
Decision checklist
- Can the workload compile to the supported Wasm/WASI target with narrow imports? Consider WebAssembly.
- Does it require unmodified Linux binaries or native tooling? Use a hardened container or VM boundary.
- Is the threat high enough that a shared host kernel is unacceptable? Add VM or machine isolation.
- Are filesystem, network, host functions, and secrets denied by default?
- Are CPU, memory, process, output, file, I/O, and time limits independently enforced?
- Are compilation and dependency installation equally sandboxed?
- Are outputs quarantined and validated before release?
- Has the exact policy passed escape and exhaustion tests?