Skip to content
GitHub Get Started
Reference

Secure Exec vs isolated-vm

Both Secure Exec and isolated-vm run untrusted JavaScript inside V8 isolates, but they operate at different layers. isolated-vm is a low-level npm library that exposes a bare V8 isolate with manual value marshaling and no system surface. Secure Exec wraps V8 isolates in a full virtualized kernel: a POSIX-like VFS, process table, socket table, permission policy, and resource limits. This page focuses on the security and performance characteristics of that core runtime layer.

Secure Execisolated-vm
LayerFull virtualized runtime around V8 isolatesBare V8 isolate primitive
System surfaceVirtualized filesystem, processes, sockets, PTYs, DNSNone (you build any surface yourself)
Host and guest dataNormal Node/POSIX I/O through the kernelManual Reference/Copy/ExternalCopy marshaling
Permission modelDeny-by-default capability policy per runtimeNone (whatever you expose is reachable)
Network egressMediated by the kernel socket table, gated by policyNone by default; any bridge you add is unmediated
Resource limitsPer-VM CPU, memory, and other capsMemory limit and timeout per isolate
npm / Node compatReal npm packages run unmodifiedNone (no module system, no node: builtins)

The core difference is how much exists inside the boundary.

  • isolated-vm gives you a raw V8 isolate and nothing else. There is no filesystem, no network, no process model, and no globals beyond the ECMAScript spec. Anything the guest can reach, you have to inject yourself across the host/guest boundary by hand.
  • Secure Exec boots a fully virtualized VM per runtime. The guest sees a POSIX-like filesystem, a process table, sockets, pipes, PTYs, and DNS, all backed by a kernel that the guest can never escape. Every guest syscall is routed through kernel-owned paths.

The trade-off: isolated-vm is a primitive you assemble a sandbox out of. Secure Exec is the assembled sandbox.

A bare V8 isolate confines memory and CPU, but it confines nothing else, because there is nothing else inside it. The security of an isolated-vm deployment is entirely a property of the bridge code you write around it.

  • What isolated-vm confines: heap memory (per-isolate limit) and execution time (per-call timeout). It cannot leak host memory directly because the guest has no references it was not given.
  • What you must build yourself: every capability the guest needs (file access, network, subprocesses) is a function you expose across the boundary. Each exposed function is attack surface, and each is unmediated unless you add your own checks.
  • What Secure Exec confines: in addition to memory and time, it enforces a deny-by-default capability policy over filesystem, network, child-process, process, and env scopes. A denied operation fails with EACCES.
  • Egress control: guest fetch(), node:http, and raw sockets flow through the kernel socket table, so outbound traffic can be allowed, denied, or rule-matched. With isolated-vm there is no networking until you build it, and once built it is not policed.
import { NodeRuntime } from "secure-exec";
// Deny-by-default: no network, filesystem and processes are virtualized.
const rt = await NodeRuntime.create();
// Opt into network egress; it is still mediated by the kernel socket table.
const networked = await NodeRuntime.create({
permissions: { network: "allow" },
});

Both runtimes execute guest code in a V8 isolate, so raw JavaScript throughput is comparable. The cost differences come from what surrounds the isolate.

  • isolated-vm is the thinner layer: creating an isolate is cheap, and the dominant per-call cost is marshaling values across the host/guest boundary, which is explicit and pay-as-you-go.
  • Secure Exec adds syscall virtualization. Guest I/O (files, sockets, processes) is routed through the kernel, so operations that touch the system carry virtualization overhead that a bare isolate does not. Pure compute that never makes a syscall runs at isolate speed.
  • Startup: Secure Exec boots a virtualized VM per runtime, which is heavier than spinning up a bare isolate, but it is far lighter than a container or microVM. Reusing a live guest process drives warm execution into the low-millisecond range. See Benchmarks for measured numbers.

The summary: isolated-vm has lower overhead because it does less; Secure Exec spends that overhead on a real system surface and enforcement you would otherwise build and pay for yourself.

This is where the two diverge most for everyday use.

  • isolated-vm: you write to the isolate API directly. There is no module loader, no node: builtins, and no normal I/O. Passing data in or out means Reference, Copy, or ExternalCopy, and any capability is a function you wrap and inject. Running an existing npm package is not a goal of the library.
  • Secure Exec: the guest sees normal Linux and Node semantics. Real npm packages run unmodified, resolved over a faithfully mounted node_modules, and kernel-backed modules (fs, net, child_process, dns, http, os) work as they would on a real machine.
import { NodeRuntime } from "secure-exec";
const rt = await NodeRuntime.create();
try {
const result = await rt.run<number>(`
__return(40 + 2);
`);
console.log(result.value); // 42
} finally {
await rt.dispose();
}
ChooseWhen
isolated-vmYou want a minimal V8 isolate primitive and intend to design and own the entire sandbox surface, capability injection, and enforcement yourself.
Secure ExecYou want to run untrusted or AI-generated code with a virtualized filesystem, process model, and networking, plus a deny-by-default permission policy and resource limits, without hand-building the sandbox.