Node.js Compatibility
Guest code in Secure Exec runs as Node.js. It never touches the host runtime: every guest import/require of a node: builtin resolves to a kernel-backed bridge or an in-isolate polyfill, never the real host module. This page describes which builtins are available and how each one is implemented.
The guest reports itself as Node v22.0.0 (process.version).
How builtins are backed
Section titled “How builtins are backed”There are three ways a builtin is provided to guest code:
- Bridge. A kernel-backed implementation. Calls route through the kernel VFS, socket table, process table, DNS resolver, or host entropy. This is how
fs,net,http,child_process,dns,os, andcryptoreach virtualized resources while staying inside the isolation boundary. - Polyfill. A pure-JavaScript implementation from
node-stdlib-browser(for examplepath,events,util,stream,zlib). These need no host access; they run entirely inside the V8 isolate. - Denied. The module is intentionally unavailable. Importing it throws an error with code
ERR_ACCESS_DENIED.
The canonical inventory lives in crates/execution/assets/polyfill-registry.json.
Bridge-backed builtins
Section titled “Bridge-backed builtins”These route through the kernel and present normal Linux/Node semantics over virtualized resources.
| Module | Backed by |
|---|---|
fs, fs/promises | Kernel VFS. File and directory operations, fds, createReadStream/createWriteStream, metadata, symlinks. watch/watchFile are guest-side polling wrappers. |
child_process | Kernel process table. spawn, exec, execFile, spawnSync, execSync, execFileSync (and the sync variants) run kernel-managed processes. See Child Processes. |
net | Kernel socket table. TCP client and server sockets, plus Unix sockets. |
dns | Kernel DNS resolver (lookup, resolve*, and the dns/promises surface). |
http, https, http2 | Built on the kernel socket path. request, get, createServer, agents with connection pooling. |
tls | Layered on the kernel net polyfill. |
os | VM-scoped values (platform, arch, hostname, CPU/memory, user info, os.constants). |
crypto | Host entropy and crypto bridges: getRandomValues, randomUUID, randomBytes, createHash, createHmac, cipher/decipher, scrypt, and crypto.subtle (WebCrypto). |
process | Virtualized process global: env (permission-gated), cwd/chdir, signals, timers, stdio, umask. |
module | createRequire, Module basics, builtin resolution, Module.builtinModules. |
console | Bridge shim with circular-safe formatting. Output is captured into the stdout/stderr result fields and can also be streamed via the onStdout/onStderr hooks on exec/run/spawn. |
dgram | Kernel socket table (UDP). |
perf_hooks, diagnostics_channel, async_hooks | Bridge-backed compatibility surfaces. |
worker_threads | Compatibility shim: isMainThread and inert ports for feature detection. Real worker threads are not spawned. |
vm | Compatibility shim: Script, createContext, isContext, runInNewContext, runInThisContext. |
v8 | Compatibility shim for safe inspection/serialization helpers. |
tty | isatty plus ReadStream/WriteStream compatibility constructors. |
readline, sqlite | Bridge-backed compatibility surfaces. |
timers, timers/promises | setTimeout, setInterval, setImmediate, and promise variants. |
stream/web, stream/consumers, stream/promises | Web Streams and stream helper subpaths. |
Network builtins (net, dgram, dns, http, https, http2, tls) are subject to the per-runtime permission policy, which denies network access by default. Operations fail until you opt in. See Permissions.
Polyfilled builtins
Section titled “Polyfilled builtins”Pure-JavaScript implementations from node-stdlib-browser, running inside the isolate. They support default and named ESM imports.
| Module | Polyfill |
|---|---|
path, path/posix, path/win32 | path-browserify |
buffer | buffer (also re-exports Blob/File) |
events | events |
stream | readable-stream |
util, util/types | node-stdlib-browser |
assert | node-stdlib-browser |
url | node-stdlib-browser shims |
querystring | node-stdlib-browser |
string_decoder | node-stdlib-browser |
zlib | node-stdlib-browser |
punycode | node-stdlib-browser |
constants | constants-browserify (os.constants stays available via os) |
console, timers | node-stdlib-browser base, with bridge wiring for stdio and the kernel clock |
sys | alias of util |
Denied builtins
Section titled “Denied builtins”Importing any of these throws an error with code ERR_ACCESS_DENIED:
cluster, domain, inspector, repl, trace_events, wasi.
Global APIs
Section titled “Global APIs”Web platform globals expected by modern npm packages are provided in the isolate, including fetch, Headers, Request, and Response. Guest fetch() runs through undici inside the V8 isolate and then through the kernel socket table, so it obeys the same network permissions as the http/net builtins. TextEncoder/TextDecoder, Buffer, URL/URLSearchParams, Blob/File, FormData, AbortController/AbortSignal, structuredClone, and performance are also available.
WebAssembly is enabled inside the isolate (WebAssembly.Module, WebAssembly.Instance, WebAssembly.instantiate*), so packages that ship .wasm work. Compilation stays inside the isolate and does not cross the isolation boundary.
Restricting the builtin surface
Section titled “Restricting the builtin surface”The builtin surface can be narrowed through the platform / allowedBuiltins config; anything excluded becomes a denied builtin (ERR_ACCESS_DENIED).
Logging behavior
Section titled “Logging behavior”console.log/warn/errorserialize arguments with circular-safe, bounded formatting.exec()andrun()buffer guest output and return it as thestdoutandstderrstrings on the result.- To observe output incrementally, pass the
onStdout/onStderrhooks, which receive rawUint8Arraychunks in emission order; the full strings are still returned when the run ends.
See Output Capture for details.
TypeScript
Section titled “TypeScript”The core runtime executes JavaScript. For sandboxed TypeScript usage, see TypeScript.
Example
Section titled “Example”Import a mix of bridge-backed and polyfilled builtins from guest code:
import { NodeRuntime } from "secure-exec";
const rt = await NodeRuntime.create();
try { const { stdout } = await rt.exec(` import path from "node:path"; import { createHash } from "node:crypto"; import { writeFileSync, readFileSync } from "node:fs";
const file = path.join("/tmp", "note.txt"); writeFileSync(file, "hello");
const digest = createHash("sha256") .update(readFileSync(file)) .digest("hex");
console.log(digest); `);
console.log(stdout.trim());} finally { await rt.dispose();}See also
Section titled “See also”- Module loading and resolution: how ESM/CJS, the
node_moduleswalk, and unmodified npm packages resolve over the virtual filesystem lives in NPM & Module Loading. - Output capture: how
stdout/stderrare buffered and streamed is documented in Output Capture.