WASM as a Secure Sandbox: From the Browser to Distributed Runners

02 Oct 2025 - tsp
Last update 02 Oct 2025
Reading time 8 mins

When I first looked into WebAssembly (WASM), I thought of it only as “that thing that lets you run high-performance code in the browser” and didn’t give it too much of relevance from my point of view - I don’t like the idea of moving everything into the browser. A closer look revealed something much more powerful: WASM is a general-purpose, sandboxed runtime. Much like the Java VM once promised with its security manager or .NETs Silverlight attempted in the browser, WASM provides a controlled execution environment for potentially untrusted code. The crucial difference is that it does so as an open standard, with a well-specified security model, and with multiple independent implementations.

This makes WASM not just interesting for web developers, but also a great tool if you need to execute third-party or user-supplied code in your own infrastructure. In my case, I wanted to safely run untrusted functions on self coordinating distributed service runners. WASM turned out to be a perfect fit.

Think of this article as a personal reference: a way to capture the workflow and reasoning so that when I revisit the topic years from now, I’ll have a clear guide to follow.

Compiling C/C++ Code with Emscripten

WASM is a low level assembly language and a binary bytecode format - so in practical day to day use its less of a programming language that you use directly (usually - I’ve written also whole applications in assembly back in the days on x86) but as a compilation target. Thanks to the versatile LLVM projects with it’s modular frontends and backends, any LLVM-fronted language (C, C++, Rust, Zig, etc.) can emit WASM. On FreeBSD, installing Emscripten is as simple as:

pkg install emscripten

Emscripten can do more than just compile C/C++ into .wasm modules. It can produce .wasm together with JavaScript glue code for browsers, or it can also emit pure JavaScript without WASM at all. In fact, historically Emscripten was first created before WASM existed, targeting asm.js and pure JavaScript as a way to run C/C++ in the browser. Its design goal was to make it possible to port complex native applications - such as SDL-based desktop games and multimedia apps - into the browser. Today, with WASM, it has become a powerful bridge between native code and both browser and non-browser runtimes.

Let’s take, for example, a tiny add.c:

int add(int a, int b) {
    return a + b;
}

One can compile this for the browser with glue code:

env BINARYEN=/usr/local/bin LLVM=/usr/local/bin emcc add.c -s WASM=1 -s EXPORTED_FUNCTIONS='["_add"]' -o add.js

This generates both add.wasm (binary) and add.js (loader). A minimal HTML harness could look like the following:

<script src="add.js"></script>
<script>
  Module.onRuntimeInitialized = () => {
    console.log("7 + 3 =", Module._add(7, 3));
  };
</script>

Emscripten can also produce so called side modules without boilerplate JavaScript, using SIDE_MODULE=2. A side module is essentially a compiled WASM unit that omits the default runtime glue code and standard library exports, leaving only the functions you explicitly choose to export. This makes it smaller and more flexible, but it also means you must provide any required imports (like callback functions) from your own JavaScript environment. Then you load the raw .wasm directly in JavaScript:

const imports = { env: { log_number: x => console.log("WASM says:", x) } };

WebAssembly.instantiateStreaming(fetch("add.wasm"), imports).then(obj => {
  console.log("7 + 4 =", obj.instance.exports.add(7, 4));
});

This demonstrates the modularity of WASM: native code compiled down to a well-defined bytecode, sandboxed by the browser or runtime.

Running WASM Outside the Browser

The real power of WASM shows when you leave the browser. There are multiple runtimes available:

For this article, I chose wasm3 as the example interpreter since it provided a straightforward and working solution where JIT‑based options failed.

The Python package of wasm3 - it is actually a C based runtime that you can embed in a variety of languages - can be installed via pip:

pip install pywasm3

Let’s take again a minimal standalone WASM module (sum.c):

int sum(int a, int b) {
    return a + b;
}

Compile it with the following Makefile:

sum.wasm: sum.c
    env BINARYEN=/usr/local LLVM=/usr/local/bin emcc sum.c -O3 -s STANDALONE_WASM=1 -Wl,--no-entry -s EXPORTED_FUNCTIONS='["_sum"]' -o sum.wasm

Now it’s extremly simple to load and execute this from Python:

import wasm3
from pathlib import Path

wasm_path = Path("sum.wasm").read_bytes()
env = wasm3.Environment()
rt  = env.new_runtime(8 * 1024)   # 8 KiB stack

mod = env.parse_module(wasm_path)
rt.load(mod)

sum_fn = rt.find_function("sum")
print("sum(21,21) =", sum_fn(21, 21))

The function executes inside wasm3’s interpreter - sandboxed, portable, and isolated from the host system.

Why WASM Is Safer

Unlike older plugin-like runtimes (Java applets, Silverlight, Flash), WASM is designed with safety as a first principle:

This makes WASM a safer building block for embedding untrusted code execution into your own systems. There is even work about provably safe sandboxing in WASM.

Why it’s not a magical solution

Please note that even though WebAssembly has been designed as a safe sandbox as with any dynamic execution environment where you run other peoples untrusted code - there is the possibility of evading the sandbox. WASM is no exception from this so there already had been known cases of escaping the sandbox via bugs in the runtime (if you really want to be safe you cannot execute arbitrary code - it’s simple as that):

Conclusion

WebAssembly has grown far beyond its browser origins. It is a portable, secure, and open sandbox for running untrusted code, making it ideal not only for web apps but also for distributed computing, serverless functions, and edge devices. With tools like Emscripten for compilation and runtimes like wasm3 or wasmtime, it’s straightforward to take C (or Rust, Go, …) code, compile it, and execute it safely across platforms.

In a world where running code you did not write yourself is increasingly common, having a reliable sandbox that is easy to embed is a big deal. This same bytecode is also increasingly adopted by cloud providers, who let users deploy short functions or transformations directly at their edge nodes. In those environments, WASM serves as a safe, portable building block for distributed data processing close to the user.

This article is tagged:


Data protection policy

Dipl.-Ing. Thomas Spielauer, Wien (webcomplains389t48957@tspi.at)

This webpage is also available via TOR at http://rh6v563nt2dnxd5h2vhhqkudmyvjaevgiv77c62xflas52d5omtkxuid.onion/

Valid HTML 4.01 Strict Powered by FreeBSD IPv6 support