One of the most interesting properties of WebAssembly is sandboxing. By default, WebAssembly instances don’t have access to anything. Only the embedder decides what resources should be exposed to it. Most systems that use Wasm take advantage of this property to build sandboxed execution environments. I would call this Ops-oriented sandboxing.
There is another way of approaching sandboxing more geared toward developers, that I have been exploring with lunatic. The idea is to limit your own permissions as the application code is advancing execution. I also like to think of it as a contract with your future self. Something like “from this point on, I will never use the filesystem again”. I believe it’s much more interesting and doesn’t get even close to the same attention as Ops-oriented sandboxing. Let’s call it Dev-oriented sandboxing.
I think everyone should be familiar with this. If you have a mobile phone, you are already used to apps asking for certain permissions before running. Basically you have a piece of software that you want to execute in a controlled environment without giving it all permissions. You as the operator that decides how much of your environment you want to expose to the software.
Tools like guix shell let you spawn one-off sandboxed environments to run some untrusted code in your terminal. Most WebAssembly runtimes are “reject everything” by default too, and require you to manually expose specific capabilities to the running WebAssembly module.
This is super useful if you are running software that someone else wrote, but if you are a developer you don’t really have control over the final execution environment. The best you can do is give permission requirements to ops.
At first, I thought that I came up with this method, but recently came across this SerenityOs blog post proving me wrong. It turns out that something similar existed in OpenBSD already 🤦.
The idea is simple, to harden our application we can reduce future permissions. This turns out to be super useful. Most of the software we write will read a configuration file in the beginning or listen on a socket. However, after this initial work it doesn’t need access to the filesystem or networking stack anymore. If we revoke our own rights after everything is set up, even if the app is compromised the attacker will only have limited permissions.
And this is complementary to Ops-oriented sandboxing and can be combined with it. An operator will still need to give your app specific permissions to get it started, but only you as a developer know when these permissions are not needed anymore.
In lunatic, we take this one step further. We allow you to spawn WebAssembly instances (we call them processes) and give them fine-grained permissions and resource constraints. They are not only a concurrency primitive, but also a tool to build more robust applications. This allows you for example to build a web server where each request is handled in a process that can use a certain amount of CPU cycles and memory in total, but also can’t access the filesystem or open other networking connections. However, if you have a specific route for uploading files, you can just give this specific handler access to the upload folder.
I would love to see more support for some kind of Dev-oriented sandboxing, not only in other Wasm runtimes, but in other systems in general. Let me know on twitter if you know of other systems that do it.