Running untrusted JavaScript safely remains one of those problems that sounds simple but isn’t. Simon Willison recently published a detailed comparison of JavaScript sandboxing approaches, covering everything from built-in Node.js features to standalone engines.
The research started small. Aaron Harper wrote about Node.js worker threads, which prompted Willison to explore whether they could help with sandboxing. He fed the question to Claude Code, which “went way beyond” the initial ask and produced a full comparison of six different sandboxing solutions.
What Got Compared
The research covers three categories of sandboxing tools:
Built-in Node.js options:
worker_threads: runs code in separate threads but shares the same processnode:vm: creates isolated contexts, though it’s not a security boundary (Node.js docs say this explicitly)- The Permission Model: Node’s newer experimental feature for restricting file system and network access
npm packages:
isolated-vm: runs V8 isolates with strict memory limits and no I/O access by defaultvm2: a wrapper aroundnode:vmwith additional protections (now deprecated due to security issues)
Alternative engines:
quickjs-emscripten: compiles the QuickJS engine to WebAssembly, giving you a completely separate JavaScript runtime- QuickJS-NG, ShadowRealm, and Deno Workers also made the list
Why This Matters
If you’re building any tool that executes user-provided JavaScript (such as plugins, low-code platforms, AI-generated code runners, or browser automation), picking the wrong sandbox can mean the difference between a secure system and a full compromise.
The key tradeoff is isolation vs. performance. Native V8 isolates (isolated-vm) run fast but share the same engine. WebAssembly-based solutions like quickjs-emscripten provide stronger isolation since they run in a completely separate runtime, but they’re slower and don’t support all modern JavaScript features.
vm2 is worth calling out specifically: despite being one of the most popular sandboxing packages on npm, it’s been deprecated after multiple sandbox escape vulnerabilities were discovered. If you’re still using it, this research is your signal to migrate.
Practical Takeaways
- For maximum security:
quickjs-emscriptengives you the strongest isolation. The untrusted code runs in a WASM-compiled engine with no access to Node.js APIs whatsoever. - For performance with good isolation:
isolated-vmis the go-to. It uses V8’s built-in isolate mechanism, which was designed for multi-tenant environments like Chrome tabs. - Avoid:
node:vmalone is not a security sandbox. The Node.js documentation explicitly warns against using it for running untrusted code. - Watch: ShadowRealm is a TC39 proposal that could eventually become the standard way to sandbox JavaScript, but it’s not production-ready yet.
The Bigger Picture
This research lands at an interesting moment. With AI coding assistants generating and executing JavaScript on the fly, the need for reliable sandboxing is growing fast. Claude Code itself was the tool that produced this comparison, a neat example of an AI system helping evaluate the security boundaries that AI systems themselves need.
What stands out here is how fragmented the landscape remains. There’s no single “right answer” for JavaScript sandboxing. Each approach trades off security, performance, compatibility, and maintenance burden differently.
For the full technical breakdown, including code examples and detailed security analysis of each approach, check out Willison’s original research post.