Secure Exec vs Cloudflare Workers
Secure Exec and Cloudflare Workers both run untrusted JavaScript inside V8, but they solve different problems. Workers is a managed, multi-tenant edge platform: you deploy code and Cloudflare runs it for you across its network. Secure Exec is a library you embed in your own application to run guest code locally inside a fully virtualized VM that you control. This page focuses on how the two differ in isolation, permissions, networking, and Node.js surface.
At a glance
Section titled “At a glance”| Secure Exec | Cloudflare Workers | |
|---|---|---|
| Form factor | Library you embed (secure-exec), runs where your app runs | Managed edge platform you deploy to |
| Isolation unit | Per runtime: each NodeRuntime.create() is its own VM and OS process | Per Worker isolate, scheduled by Cloudflare |
| Guest runtime | V8 isolate inside a virtualized POSIX kernel (filesystem, processes, sockets, PTYs) | V8 isolate with the workerd runtime |
| Permissions | Deny-by-default capability policy you configure per runtime | Platform-managed; no per-call capability policy |
| Subprocesses | Real node:child_process against kernel-managed processes | Not available |
| Filesystem | Full virtualized filesystem per runtime | Limited in-memory node:fs surface, ephemeral |
| You operate it | Yes (your process, your machine) | No (Cloudflare operates it) |
Isolation model
Section titled “Isolation model”This is the most important difference, and the easiest one to misstate.
Cloudflare Workers isolates code dynamically. Cloudflare’s runtime schedules many Workers into a pool of V8 isolates and can move work between them, with platform-level mitigations (such as I/O-gated clock coarsening) layered on to defend against side-channel attacks across that shared infrastructure.
Secure Exec isolates code statically, one boundary per runtime. Every NodeRuntime.create() boots a fully virtualized VM backed by its own sidecar process. Two runtimes share nothing: not the filesystem, not globals, not module state, and not crash fate. Secure Exec does not reassign guest code between processes at runtime, and it does not claim Cloudflare-style dynamic, cross-tenant isolate scheduling. You decide the blast radius by deciding how many runtimes to create and what to put in each one.
Within a single runtime, every exec() or run() call runs in a fresh guest process, so in-memory state from one run does not leak into the next.
The practical consequence: with Workers you trust Cloudflare’s platform to keep tenants apart. With Secure Exec you get a hard, statically-defined boundary per runtime that you place yourself, which is well suited to running one tenant or one task per runtime and disposing it when finished.
Permissions
Section titled “Permissions”Cloudflare Workers does not expose a per-invocation capability policy. What a Worker can reach is governed by its bindings and platform configuration.
Secure Exec applies a deny-by-default capability policy per runtime. Network access is denied until you opt in; the virtualized filesystem, child-process, process, and env scopes are enabled so programs can run at all (the guest only ever sees the VM, never the real host). You tighten or widen any scope when you create the runtime.
import { NodeRuntime } from "secure-exec";
// Default: no network access. Filesystem and processes are virtualized.const sandboxed = await NodeRuntime.create();
// Opt into network access while keeping the other defaults.const networked = await NodeRuntime.create({ permissions: { network: "allow" },});Networking
Section titled “Networking”Inside the VM, guest fetch() runs through undici in the V8 isolate and then through the kernel’s socket table, gated by the network permission. Guest code can also bind ports inside the VM: Secure Exec exposes findListener() and waitForListener() so the host can detect when a guest process starts listening (for example, to wait for an in-VM HTTP server before sending it a request).
From the host side, rt.fetch(port, input) drives an HTTP request into a guest server listening inside the VM and reads the response back. The request never leaves the VM (it connects to the guest’s loopback listener through the kernel socket table), so it works even when guest network egress is denied.
Cloudflare Workers provides outbound fetch() and a Sockets API for outbound TCP/TLS, but a Worker does not listen on a port the way a normal server does; it responds to incoming requests routed by the platform.
import { NodeRuntime } from "secure-exec";
const rt = await NodeRuntime.create();try { // Start a long-running guest HTTP server and get a live handle back. const server = await rt.spawn(` import http from "node:http"; http.createServer((_req, res) => res.end("ok")).listen(3000); `);
// Block until the listener is up, then drive a request into it from the host. await rt.waitForListener({ port: 3000 }); const res = await rt.fetch(3000, { path: "/" }); console.log(res.status, res.body); // 200 ok
server.kill(); await server.wait();} finally { await rt.dispose();}Subprocesses and the POSIX surface
Section titled “Subprocesses and the POSIX surface”Secure Exec presents a virtualized POSIX environment. Guest code can use node:child_process to spawn commands that exist inside the VM (such as sh and the mounted coreutils), and every child is a kernel-managed process, never a real host process. The host can also start a long-running guest program and drive it with rt.spawn().
Cloudflare Workers has no subprocess model. There is no child_process, no process table, and no shell.
Node.js compatibility
Section titled “Node.js compatibility”Both runtimes aim to present Node.js semantics on top of V8, and both implement a large subset of the Node.js standard library. The character of the gaps differs:
- Secure Exec is backed by a real virtualized kernel, so capabilities that need an OS, a full filesystem, real child processes, sockets that listen, and a process table, are first-class. Pure-JS builtins are provided through
node-stdlib-browser, and kernel-backed modules (such asfs,net,child_process,dns,http,os) are wired through the bridge to the kernel. - Cloudflare Workers provides Node.js compatibility through the
nodejs_compatflag on top ofworkerd. Its filesystem is a limited in-memory surface, there is no subprocess support, and listening servers are replaced by the request/response handler model. In exchange it ships a comprehensive nativenode:cryptoand Web Crypto surface and platform-native logging.
When to choose which
Section titled “When to choose which”Choose Cloudflare Workers when you want a managed, globally distributed platform to deploy your own trusted code to, with the operations handled for you.
Choose Secure Exec when you need to run untrusted or AI-generated code inside your own application with a hard, self-placed isolation boundary, a real virtualized filesystem and process model, and a capability policy you control. One runtime per tenant or per task gives you a clean blast radius you can dispose on demand.