Skip to main content
Comparison

Secure Exec vs QuickJS

How Secure Exec and quickjs-emscripten differ in isolation, performance, resource limits, and what guest code can do.

QuickJS is a small JavaScript engine written in C. Node.js developers almost always use it through quickjs-emscripten, which is QuickJS compiled to WebAssembly, so that is what this page compares. It gives you an interpreter and nothing else: you build the sandbox around it. Secure Exec is a complete virtualized runtime on V8, with a kernel, a virtual filesystem, and a permission policy.

At a glance

Secure Execquickjs-emscripten
EngineV8 with JITInterpreter compiled to WebAssembly
Where guest code runsA separate sidecar processYour thread, unless you add workers
Guest capabilitiesNode.js builtins, virtual filesystem, processes, npmECMAScript only. No console, timers, or fetch
Host and guest dataJSON in and out, host functionsHandles you create and dispose by hand
Resource limitsTimeouts, heap, processes, sockets, filesystem bytesA memory limit and an interrupt handler, both with known gaps
Intl, Temporal, WebAssemblyAvailableNot available
TypeScriptRuns, and type-checks with the real compilerNot handled
Runs inNode.js on Linux and macOSBrowsers, Node.js, Deno, Bun, edge runtimes

Where quickjs-emscripten falls short

  • Nothing is provided. The README says: “By default, no host functionality is exposed.” There is no console, setTimeout, fetch, require, process, Buffer, URL, or crypto until you write and inject them. There is no npm resolution and no filesystem.
  • You manage memory by hand. Every value is a handle, and “you must manually manage their memory by calling a .dispose() method”. A leaked handle makes disposing the runtime fail an assertion. The README’s own testing section begins: “This library is complicated to use”.
  • It blocks your event loop. Guest code runs synchronously on your thread, and guest promises only make progress when you call executePendingJobs(). A busy guest stops your server from doing anything else unless you move it to a worker yourself.
  • Async host calls cost speed and size. The asyncify build that makes them possible has “substantial size (2x the size of sync) and speed penalties (40% the speed of sync)”, and a module “can only suspend to wait for a single asynchronous call at a time”.
  • The memory limit does not bound memory. Open issues #271 and #255 report allocations that ignore setMemoryLimit. We reproduced it: under an 8 MB limit, guest code allocated 500 MB of strings and the host’s memory stayed there after every runtime was disposed.
  • The interrupt handler has gaps. Open issue #219 reports that it is not consulted inside long built-in operations. With a 50 ms deadline, new Array(5e7) + "" ran for 882 ms.
  • Parts of the platform are missing. There is no Intl, Temporal, Atomics, or WebAssembly.
  • It has not been audited. The README says it “has not been audited. Please use with care in production settings”, that the author works on it “in my free time”, and to “expect occasional breaking API changes”. Releases 0.31 and 0.32 were 17 months apart.

Performance

QuickJS is an interpreter with no JIT, and quickjs-emscripten runs it inside WebAssembly. In our benchmarks it took 3.1 to 10.1 times longer than Secure Exec to do the same computation, and asyncify builds are slower again. It starts much faster: well under a millisecond per context, against 14 to 40 ms for a Secure Exec call. For a three-line snippet that is what you will notice. For code that parses, transforms, or validates data, the interpreter is what you will notice, and all of that time blocks your event loop.

What quickjs-emscripten does well

It has no native binary, so it runs in browsers, Deno, Bun, and edge runtimes, where Secure Exec cannot. It is small and starts almost instantly. The WebAssembly layer confines memory-safety bugs in the engine, which keep being found, so an engine bug cannot inject code into your host. Its core language conformance is excellent.

Only choose quickjs-emscripten when

All of these hold:

  1. The code must run where no native process can, such as a browser or an edge worker.
  2. Guest code is short, pure ECMAScript that needs no Node.js APIs, npm, filesystem, network, Intl, or TypeScript, and you accept writing and disposing every bridge by hand.
  3. You do not care about performance. Code that runs several times slower, all of it blocking your event loop, is acceptable.
  4. You will supply your own hard limits, such as a worker thread with a kill timer, because the built-in memory limit and interrupt handler have open, reproducible gaps.

Choose Secure Exec when

You run Node.js on a server and need npm and Node.js compatibility, JIT speed, limits that hold, and an isolation boundary you do not have to build and audit yourself.