A WebAssembly module only ever sees its own linear memory — one contiguous block the host allocates for it. There is no pointer, valid or forged, that can address memory outside that block: even a buffer-overflow bug inside the module cannot corrupt the host or another module's memory, because the memory-safety check happens at the boundary of the block itself, not inside the module's own logic.
WASM also has no direct system calls. A module cannot ask the OS to open a file or a socket on its own — it can only call functions the host explicitly imported into it. This is capability-based security: the host decides exactly which functions exist inside the sandbox, and anything not imported simply cannot be called, the same way a name that was never declared cannot be referenced.
write(ptr, len) →
if ptr+len within [0, memory.byteLength):
write succeeds inside linear memory
else:
trapped at the memory bound · overflow blocked
call(hostFn, args) →
if hostFn in module.imports:
call reaches the host · allowed
else:
no such function exists · blocked (no direct syscalls)
- Toggle fs.open / net.connect to see the host grant or revoke a capability live — the next syscall attempt respects the new state.
- process.spawn is locked: real WASM has no raw process/syscall import at all, so this one can never be granted, toggle or not.
- Memory overflow attempt pushes a write past the last page — it is trapped exactly at the linear-memory bound, never touching anything beyond it.
- Unauthorized syscall attempt calls a function currently outside the import table — it is rejected before it ever reaches the host, because the capability was never granted.
Real-world relevance: this is why a WASM module compiled from untrusted C/C++ (or Rust) can safely run in a browser tab or on a server next to other tenants — a bug inside it stays inside its own memory block, and it can only do what the embedding host chose to expose to it.