Target Node Version
22.x (derived from the @types/node 22.x validation baseline used by tests and type checks).
Architecture
secure-exec runs Node.js code inside a V8 isolate with a virtual kernel that mediates all system access. Nothing in the sandbox touches the host OS directly:- All I/O routes through the virtual kernel.
fs.readFile()goes through the VFS,http.request()goes through the network stack,child_process.spawn()goes through the process table. The kernel enforces permissions at every boundary. - Network calls are kernel-mediated.
http.createServer()registers a virtual listener in the kernel’s network stack.http.request()to localhost routes through the kernel without touching real TCP — the kernel connects the virtual server to the virtual client directly. External requests go through the host adapter after permission checks. - The VFS is not the host file system. Files written by sandbox code live in the VFS (in-memory by default). The host file system is accessible only through explicit read-only overlays (e.g.,
node_modules) configured by the embedder. - Embedders provide host adapters that implement the actual I/O. A Node.js embedder provides real
fsandnet; a browser embedder providesfetch-based networking and no file system. The sandbox code doesn’t know or care which adapter backs the kernel.
Support Tiers
| Tier | Label | Meaning |
|---|---|---|
| 1 | Bridge | Runtime implementation in secure-exec bridge modules. |
| 2 | Polyfill | Browser-compatible polyfill package implementation. |
| 3 | Stub | Minimal compatibility surface for lightweight usage and type/instance checks. |
| 4 | Deferred | require() succeeds, but APIs throw deterministic unsupported errors on call. |
| 5 | Unsupported | Not implemented by design; require() throws immediately. |
"<module>.<api> is not supported in sandbox".
Unsupported modules use: "<module> is not supported in sandbox".
Compatibility Matrix
| Module | Tier | Status |
|---|---|---|
fs | 1 (Bridge) + 4 (Deferred APIs) | Implemented: readFile, writeFile, appendFile, open, read, write, close, readdir, mkdir, rmdir, rm, unlink, stat, lstat, rename, copyFile, exists, createReadStream, createWriteStream, writev, access, realpath, chmod, chown, link, symlink, readlink, truncate, utimes, cp, mkdtemp, opendir, glob, statfs, readv, fdatasync, fsync. Metadata-sensitive operations (stat, exists, readdir with withFileTypes) use metadata-native driver paths instead of content probing. rename delegates to driver semantics (atomic where supported; explicit limitation errors where not). Deferred watcher APIs: watch, watchFile, fs.promises.watch. |
process | 1 (Bridge) | Env access (permission-gated), cwd/chdir, exit semantics, timers, stdio, eventing, and basic usage/system metadata APIs. |
os | 1 (Bridge) | Platform/arch/version, user/system info, and os.constants. |
child_process | 1 (Bridge) + 5 (fork) | Implemented: spawn, spawnSync, exec, execSync, execFile, execFileSync; fork is intentionally unsupported. |
http | 1 (Bridge) | Implemented: request, get, createServer; bridged request/response/server classes and constants. Includes Agent with connection pooling (maxSockets, keepAlive), HTTP upgrade (101 Switching Protocols) handling, and trailer headers support. |
https | 1 (Bridge) | Implemented: request, get, createServer with the same contract as http, including Agent pooling, upgrade handling, and trailer headers. |
http2 | 3 (Stub) + 5 (Full support) | Provides compatibility classes (Http2ServerRequest, Http2ServerResponse); createServer and createSecureServer are unsupported. |
dns | 1 (Bridge) | Implemented: lookup, resolve, resolve4, resolve6, plus dns.promises variants. |
module | 1 (Bridge) | Implements createRequire, Module basics, and builtin resolution (require.resolve("fs") returns builtin identifiers). |
timers | 1 (Bridge) | setTimeout, clearTimeout, setInterval, clearInterval, setImmediate, clearImmediate. |
path | 2 (Polyfill) | path-browserify; supports default and named ESM imports. |
buffer | 2 (Polyfill) | Polyfill via buffer. |
url | 2 (Polyfill) | Polyfill via whatwg-url and node-stdlib-browser shims. |
events | 2 (Polyfill) | Polyfill via events. |
stream | 2 (Polyfill) | Polyfill via readable-stream. stream/web subpath supported (Web Streams API: ReadableStream, WritableStream, TransformStream, etc.). |
util | 2 (Polyfill) | Polyfill via node-stdlib-browser. |
assert | 2 (Polyfill) | Polyfill via node-stdlib-browser. |
querystring | 2 (Polyfill) | Polyfill via node-stdlib-browser. |
string_decoder | 2 (Polyfill) | Polyfill via node-stdlib-browser. |
zlib | 2 (Polyfill) | Polyfill via node-stdlib-browser. |
vm | 2 (Polyfill) | Polyfill via node-stdlib-browser. |
punycode | 2 (Polyfill) | Polyfill via node-stdlib-browser. |
crypto | 3 (Stub) | Limited bridge/polyfill blend; getRandomValues() and randomUUID() use host node:crypto secure randomness, or throw deterministic unsupported errors if host entropy is unavailable; subtle.* methods throw deterministic unsupported errors. |
tty | 2 (Polyfill) | tty-browserify; isatty() returns false; ReadStream/WriteStream are compatibility constructors. |
v8 | 3 (Stub) | Pre-registered stub with mock heap stats and JSON-based serialize/deserialize. |
constants | 2 (Polyfill) | constants-browserify; os.constants remains available via os. |
Fetch globals (fetch, Headers, Request, Response) | 1 (Bridge) | Bridged via network bridge implementation. |
async_hooks | 3 (Stub) | AsyncLocalStorage (with run, enterWith, getStore, disable, exit), AsyncResource (with runInAsyncScope, emitDestroy), createHook (returns enable/disable no-ops), executionAsyncId, triggerAsyncId. |
console | 1 (Bridge) | Circular-safe bounded formatting via bridge shim; log, warn, error, info, debug, dir, time/timeEnd/timeLog, assert, clear, count/countReset, group/groupEnd, table, trace. Drop-by-default; consumers use onStdio hook for streaming. |
diagnostics_channel | 3 (Stub) | No-op channel(), tracingChannel(), Channel constructor; channels always report hasSubscribers: false; publish, subscribe, unsubscribe are no-ops. Provides Fastify compatibility. |
Deferred modules (net, tls, readline, perf_hooks, worker_threads) | 4 (Deferred) | require() returns stubs; APIs throw deterministic unsupported errors when called. |
Unsupported modules (dgram, cluster, wasi, inspector, repl, trace_events, domain) | 5 (Unsupported) | require() fails immediately with deterministic unsupported-module errors. |
ESM Execution Notes
exec(code, { filePath })honors Node-style ESM classification for.mjsentrypoints and.jsfiles underpackage.json"type": "module".- ESM package resolution uses
importconditions, whilerequire()andcreateRequire()keeprequirecondition semantics in the same execution. - Built-in ESM imports such as
node:fsandnode:pathsupport both default exports and named exports for supported APIs. - Dynamic
import()preserves underlying missing-module, syntax, and evaluation failures instead of collapsing them into successful execution results.
Permanently Unsupported Features
Some Node.js features cannot be supported in secure-exec due to fundamental architectural constraints of the sandboxed V8 isolate. These are not planned for implementation.Modules
| Module | Reason |
|---|---|
cluster | Requires multi-process coordination with shared server handles and IPC. The sandbox runs a single V8 isolate — there is no process table to fork into. |
worker_threads | Requires spawning OS threads with separate V8 isolates and shared memory (SharedArrayBuffer). The sandbox architecture is single-isolate-per-session. |
vm | vm.createContext() and vm.runInNewContext() require creating isolated V8 contexts within the same isolate. The sandbox provides one context. vm.runInThisContext() works (equivalent to eval). |
inspector | Exposes the V8 debugger protocol (breakpoints, heap snapshots, CPU profiling). This is a security surface that allows arbitrary code introspection and cannot be exposed in a sandbox. |
domain | Deprecated since Node.js v4 and removed from documentation. Not worth implementing — use async_hooks or structured error handling instead. |
repl | Interactive read-eval-print loop requiring full terminal integration, tab completion, and command history. Not meaningful in a sandboxed execution context. |
trace_events | Requires V8 tracing infrastructure and file-based trace log output. Not available in the sandboxed isolate. |
wasi | WASI (WebAssembly System Interface) requires a separate WASM runtime within the V8 isolate. Not applicable — secure-exec IS the sandbox. |
APIs
| API | Reason |
|---|---|
child_process.fork() | Creates a new Node.js process with IPC channel. Requires a real node binary and multi-process architecture. spawn/exec/execFile ARE supported. |
process.execPath | Returns path to the Node.js binary. The sandbox has no node binary on disk — code runs in an embedded V8 isolate, not a Node.js process. |
V8 CLI flags (--expose-gc, --expose-internals, --harmony-*) | The V8 isolate is pre-configured at sandbox creation time. Runtime flag injection is a security risk. --expose-gc may be supported in the future as a bridge call. |
V8 snapshot APIs (v8.writeHeapSnapshot, v8.startupSnapshot) | Require direct V8 C++ API access for heap serialization. Not exposed through the bridge. |
Native addons (.node files) | Require loading compiled shared libraries (.so/.dylib/.dll) into the process. The sandbox does not permit native code execution. |
Behaviors
| Behavior | Reason |
|---|---|
Real OS signals (SIGTERM, SIGUSR1, etc.) | The sandbox is not an OS process — it’s a V8 isolate within a host process. There are no real POSIX signals to deliver. process.on('SIGINT') may be emulated in the future. |
Real file system watchers (fs.watch, fs.watchFile, fs.promises.watch) | The VFS (virtual file system) has no inotify/kqueue/FSEvents-equivalent primitive. These APIs fail fast with deterministic unsupported errors instead of hanging while waiting for events that the sandbox cannot produce. |
| Multi-context execution | The sandbox runs one V8 context per isolate. Features requiring context isolation (ShadowRealm, vm.createContext) cannot work. |
| QUIC protocol | Experimental in Node.js, depends on tls + net + OpenSSL QUIC support. Not planned. |
Tested Packages
The project-matrix test suite validates that real-world npm packages produce identical output in secure-exec and host Node.js. Each fixture is a black-box Node project with no sandbox-specific code.| Package | Category | What It Tests |
|---|---|---|
| express | Web Framework | HTTP server, middleware, routing |
| fastify | Web Framework | Async middleware, schema validation, plugins |
| next | Web Framework | React SSR, module resolution, build tooling |
| vite | Build Tool | ESM, HMR server, plugin system |
| astro | Web Framework | Island architecture, SSR, multi-framework |
| hono | Web Framework | ESM imports, lightweight HTTP |
| axios | HTTP Client | HTTP client requests via fetch adapter, JSON APIs |
| node-fetch | HTTP Client | Fetch polyfill using http module, stream piping |
| dotenv | Configuration | Environment variable loading, fs reads |
| semver | Utility | Version parsing and comparison |
| ssh2 | Networking | SSH client/server, crypto, streams, events |
| ssh2-sftp-client | Networking | SFTP client, file transfer APIs over SSH |
| pg | Database | PostgreSQL client, Pool/Client classes, type parsers |
| mysql2 | Database | MySQL client, connection/pool classes, escape/format utilities |
| ioredis | Database | Redis client, Cluster, Command, pipeline/multi transaction APIs |
| drizzle-orm | Database | ORM schema definition, query building, ESM module graph |
| ws | Networking | WebSocket client/server, HTTP upgrade, events |
| jsonwebtoken | Crypto | JWT signing (HS256), verification, decode |
| bcryptjs | Crypto | Pure JS password hashing and verification |
| chalk | Terminal | Terminal string styling, ANSI escape codes |
| lodash-es | Utility | Large ESM module resolution at scale |
| pino | Logging | Structured JSON logging, child loggers, serializers |
| uuid | Crypto | UUID generation (v4, v5), validation, version detection |
| yaml | Utility | YAML parsing, stringifying, document API |
| zod | Validation | Schema definition, parsing, safe parse, transforms |
| rivetkit | SDK | Local vendor package resolution |
| crypto (builtin) | Crypto | crypto.randomBytes, randomUUID, getRandomValues |
| fs-metadata-rename | Filesystem | stat metadata, rename semantics |
| module-access | Module Resolution | Node modules overlay access |
| conditional-exports | Module Resolution | Package exports field resolution |
| optional-deps | Module Resolution | Optional dependency handling |
| peer-deps | Module Resolution | Peer dependency resolution |
| transitive-deps | Module Resolution | Transitive dependency chains |
| pnpm-layout | Package Manager | pnpm node_modules structure |
| npm-layout | Package Manager | npm flat node_modules layout |
| yarn-classic-layout | Package Manager | Yarn v1 node_modules layout |
| yarn-berry-layout | Package Manager | Yarn Berry PnP/node_modules layout |
| bun-layout | Package Manager | Bun node_modules layout |
| workspace-layout | Package Manager | npm workspace node_modules layout |
| sse-streaming | Networking | SSE server, chunked transfer-encoding, streaming reads |
| net-unsupported (fail) | Error Handling | net.createServer correctly errors |
Logging Behavior
console.log/warn/errorare supported and serialize arguments with circular-safe bounded formatting.exec()/run()results do not expose bufferedstdout/stderrfields.- By default, secure-exec drops console emissions instead of buffering runtime-managed output.
- Consumers that need logs should use the explicit
onStdiohook to streamstdout/stderrevents in emission order.
TypeScript Workflows
- Core
secure-execruntimes execute JavaScript only. - Sandboxed TypeScript type checking and compilation belong in the separate
@secure-exec/typescriptpackage.
Node-Modules Overlay Behavior
- Node runtime composes a read-only
/app/node_modulesoverlay from<cwd>/node_modules(defaultcwdis hostprocess.cwd(), configurable viamoduleAccess.cwd). - Overlay reads are constrained to canonical paths under
<cwd>/node_modulesand fail closed on out-of-scope symlink/canonical escapes. - Writes and mutations under
/app/node_modules/**are denied withEACCES. - Native addons (
.node) are rejected in overlay-backed module loading.
Permission Model (Runtime/Bridge Scope)
- This section describes the core runtime/bridge contract only.
- Runtime permissions are deny-by-default for
fs,network,childProcess, andenv. - If a domain checker is not configured, operations fail with
EACCES. filterEnvstrips environment variables unlesspermissions.envexplicitly allows them.- Embedders can opt in via explicit permission policies such as
allowAll,allowAllFs,allowAllNetwork,allowAllChildProcess, andallowAllEnv. - Driver-specific convenience defaults (for example, direct
createNodeDriver(...)usage when adapters are provided without an explicitpermissionspolicy) are implementation details and are not the canonical runtime/bridge security contract.