I was a fan of Deno's security model
This is in response to “I’m not a fan of Deno’s security model”.
I spent most of the last four years working on Val Town. When I joined, we were running JavaScript code in vm2, hoping that it was a sufficient sandbox. It was not. I advocated for Deno and we switched to Deno, in large part because it provided a stronger sandbox.
This decision wasn’t perfect: most of Deno’s core ideas lost in the end.
- Node compatibility won out. Bun won a lot of users by being Node-compatible. Yes, a lot of Node-like APIs landed in WinterTC, but lots didn’t. Actually the direction is toward a larger Node core, that includes testing, SQLite, and TypeScript support. Stuff that’s long been obviously userspace, like generating ZIP files, is making its way to Node core. So, this is the radical opposite of the Deno philosophy in which the core would be small, mostly standardized JavaScript globals. Deno backtracked and implemented Node compatibility, but it was too little too late.
- Deno’s HTTP imports, which were a convenient feature for Val Town’s semantics - import code cross-account with zero friction - got rolled back again and again until Deno’s module system was identical to Node’s. The innovation on the module loading front is evolutionary, in the vlt.io sense, not radical like Deno thought it would be.
But: Deno’s security model. In short: it worked. We bet the company on the security of Deno’s runtime flags, and there were absolutely zero cases in which they were the culprit in a security sense. Now we were using it in a different way than the linked blog post describes: from day one, with --no-prompt. Using a program and interactively approving or denying permissions is not what I imagine as a core usecase for the permissions system: the prompts are really there for you to learn what permissions are necessary and then bake them into configuration going forward. We also didn’t enable --allow-run for anything, and yes, it’s confusing. Same with --allow-env, code in Val Town always ran with an artificial environment. The linked post sort of supposes a different use-case - running new code interactively and wanting to loosen the sandbox a bit. If you run the code non-interactively with a strict sandbox, it’s pretty good?
Now: bwrap. cgroups. Linux namespaces. The correct way to implement this system. Yes, I knew about all of that. And the decision of whether to switch from Deno’s process-based perhaps-faux-isolation to Linux’s strong, generic isolation was a constant topic of conversation.
The suggestion is always to just bubblewrap it. Or to just throw some cgroups on it. It’s always easy in abstract. On your local computer, sure, why not! Mostly we were aiming for cgroups, and it turns out that our hosting provider, as well as nearly every PaaS provider, runs processes inside of Docker, and unless you bless the Docker startup with --cgroupns=host to grant it privileges to mess with the host’s cgroups settings, cgroups will not work. That rules out hosts like Render, Railway, etc. The same goes for linux namespaces: doing them on an unprivileged host is hard or impossible. And sure, you can control everything from top to bottom if you run your own servers on metal, but that requires a lot of devops commitment.
Deno, on the other hand, works anywhere. It works the same on macOS and Windows, unlike bubblewrap, so you can reliably test it on developer machines. It’s clean and simple to deploy and scale.
And personally, I think that language VMs implementing controls like this is cool. For example, Starlark calls its execution ‘hermetic’ by default - unable to access the surrounding environment. Lua supports sandboxing too. Probably the ultimate would be an in-language effect implementation in which a bootstrap script could declare which resources an import is able to use - Effect in TypeScript can’t do that, but I believe Koka can.
In this era of untrusted code and exploits everywhere, wouldn’t it be cool if you could import some NPM module for doing date math and say up front that it can’t access the filesystem or environment variables? OS-level capabilities controls are powerful and reliable but coarse. Language-level controls could add an extra level trust & compartmentalization for applications.
Deno’s security model isn’t perfect. But it was there when we needed it, its guarantees held, and it solved a real problem, and for that I appreciate it.