Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
cc1b979
feat: US-166 - Update cloudflare-workers-comparison.mdx with current …
NathanFlurry Mar 19, 2026
7458c20
chore: update progress for US-166
NathanFlurry Mar 19, 2026
e086001
chore: update progress for US-166
NathanFlurry Mar 19, 2026
30b63df
feat: US-167 - Verify nodejs-compatibility.mdx and cloudflare-workers…
NathanFlurry Mar 19, 2026
dc1a784
chore: update progress for US-167
NathanFlurry Mar 19, 2026
bd9ad03
feat: US-168 - Implement crypto.createHash and crypto.createHmac in b…
NathanFlurry Mar 19, 2026
3c9a1aa
chore: update progress for US-168
NathanFlurry Mar 19, 2026
878444d
feat: US-169 - Implement crypto.randomBytes, randomInt, and randomFil…
NathanFlurry Mar 19, 2026
7ec3c31
feat: US-170 - Implement crypto.pbkdf2 and crypto.scrypt in bridge
NathanFlurry Mar 19, 2026
f10960b
feat: US-171 - Implement crypto.createCipheriv and crypto.createDecip…
NathanFlurry Mar 19, 2026
28aef48
chore: update progress for US-171
NathanFlurry Mar 19, 2026
3c9d7a9
feat: US-172 - Implement crypto.sign, crypto.verify, and key generati…
NathanFlurry Mar 19, 2026
47bce8c
feat: US-173 - Implement crypto.subtle (Web Crypto API) in bridge
NathanFlurry Mar 19, 2026
6c3b620
feat: US-174 - Add ssh2 project-matrix fixture
NathanFlurry Mar 19, 2026
5ad2b08
feat: US-175 - Add ssh2-sftp-client project-matrix fixture
NathanFlurry Mar 19, 2026
f53d11c
chore: update progress for US-175
NathanFlurry Mar 19, 2026
2f875b1
feat: US-176 - Add pg (node-postgres) project-matrix fixture
NathanFlurry Mar 19, 2026
a340a0f
feat: US-177 - Add drizzle-orm project-matrix fixture
NathanFlurry Mar 19, 2026
38aa577
chore: update progress for US-177
NathanFlurry Mar 19, 2026
c4e2e75
feat: US-178 - Add axios project-matrix fixture
NathanFlurry Mar 19, 2026
8927e07
chore: update progress for US-178
NathanFlurry Mar 19, 2026
1d574ae
feat: US-179 - Add ws (WebSocket) project-matrix fixture
NathanFlurry Mar 19, 2026
bb2a9c0
chore: update progress for US-179
NathanFlurry Mar 19, 2026
080499a
feat: US-180 - Add zod project-matrix fixture
NathanFlurry Mar 19, 2026
a7c64f1
chore: update progress for US-180
NathanFlurry Mar 19, 2026
88173ca
feat: US-181 - Add jsonwebtoken project-matrix fixture
NathanFlurry Mar 19, 2026
fb76520
feat: US-182 - Add bcryptjs project-matrix fixture
NathanFlurry Mar 19, 2026
3424d66
chore: update progress for US-182
NathanFlurry Mar 19, 2026
7bb654c
feat: US-183 - Add lodash-es project-matrix fixture
NathanFlurry Mar 19, 2026
3e3950e
chore: update progress for US-183
NathanFlurry Mar 19, 2026
c458f5d
feat: US-184 - Add chalk project-matrix fixture
NathanFlurry Mar 19, 2026
78b56da
chore: update progress for US-184
NathanFlurry Mar 19, 2026
7afdbeb
feat: US-185 - Add pino project-matrix fixture
NathanFlurry Mar 19, 2026
d09ebb8
chore: update progress for US-185
NathanFlurry Mar 19, 2026
32e9982
feat: US-186 - Add node-fetch project-matrix fixture
NathanFlurry Mar 19, 2026
9afb6e9
chore: update progress for US-186
NathanFlurry Mar 19, 2026
9947cae
feat: US-187 - Add yaml project-matrix fixture
NathanFlurry Mar 19, 2026
f86aea1
chore: update progress for US-187
NathanFlurry Mar 19, 2026
862aae4
feat: US-188 - Add uuid project-matrix fixture
NathanFlurry Mar 19, 2026
afa5915
chore: update progress for US-188
NathanFlurry Mar 19, 2026
922ffa6
feat: US-189 - Add mysql2 project-matrix fixture
NathanFlurry Mar 19, 2026
79d4227
chore: update progress for US-189
NathanFlurry Mar 19, 2026
4eaf21e
feat: US-190 - Add SSE (Server-Sent Events) streaming project-matrix …
NathanFlurry Mar 19, 2026
181ff8c
chore: update progress for US-190
NathanFlurry Mar 19, 2026
b97d4a9
feat: US-191 - Add WebSocket project-matrix fixture using ws package
NathanFlurry Mar 19, 2026
2b08441
chore: update progress for US-191
NathanFlurry Mar 19, 2026
112214f
feat: US-192 - Create shared Docker container test utility for integr…
NathanFlurry Mar 19, 2026
66ec71a
chore: update progress for US-192
NathanFlurry Mar 19, 2026
d38506e
feat: US-193 - Add ioredis project-matrix fixture
NathanFlurry Mar 19, 2026
b7aee0f
chore: update progress for US-193
NathanFlurry Mar 19, 2026
7071492
feat: US-194 - Update mysql2 fixture to use real MySQL via Docker
NathanFlurry Mar 19, 2026
91700c4
chore: update progress for US-194
NathanFlurry Mar 19, 2026
a28d515
feat: US-195 - Fix node -e stdout not appearing in interactive shell
NathanFlurry Mar 19, 2026
47b969b
chore: update progress for US-195
NathanFlurry Mar 19, 2026
976ff2d
feat: US-196 - Fix node -e stderr/errors not appearing in interactive…
NathanFlurry Mar 19, 2026
4fc3e21
chore: update progress for US-196
NathanFlurry Mar 19, 2026
5382192
feat: US-197 - Fix tree command hanging in interactive shell
NathanFlurry Mar 19, 2026
ff386f9
chore: update progress for US-197
NathanFlurry Mar 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .agent/contracts/node-stdlib.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,13 @@ The `crypto` module SHALL be classified as Stub (Tier 3). `getRandomValues()` an

#### Scenario: Calling crypto.subtle.digest
- **WHEN** sandboxed code calls `crypto.subtle.digest("SHA-256", data)`
- **THEN** the call MUST throw an error indicating subtle crypto is not supported in sandbox
- **THEN** the call MUST delegate to host `node:crypto` and return an ArrayBuffer containing the hash

#### Scenario: crypto.subtle operations delegate to host
- **WHEN** sandboxed code calls any `crypto.subtle` method (digest, encrypt, decrypt, sign, verify, generateKey, importKey, exportKey)
- **THEN** the operation MUST delegate to host `node:crypto` via the `_cryptoSubtle` bridge ref
- **AND** all cryptographic material MUST be transferred as base64-encoded JSON across the isolate boundary
- **AND** CryptoKey objects in the sandbox MUST be opaque wrappers holding serialized key data

### Requirement: Unimplemented Module Tier Assignments
The following modules SHALL be classified as Deferred (Tier 4): `net`, `tls`, `readline`, `perf_hooks`, `async_hooks`, `worker_threads`, `diagnostics_channel`. The following modules SHALL be classified as Unsupported (Tier 5): `dgram`, `http2` (full), `cluster`, `wasi`, `inspector`, `repl`, `trace_events`, `domain`.
Expand Down
19 changes: 19 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,16 @@
- track development friction in `docs-internal/friction.md` (mark resolved items with fix notes)
- see `.agent/contracts/README.md` for the full contract index

## Shell & Process Behavior (POSIX compliance)

- the interactive shell (brush-shell via WasmVM) and kernel process model must match POSIX behavior unless explicitly documented otherwise
- `node -e <code>` must produce stdout/stderr visible to the user, both through `kernel.exec()` and in the interactive shell PTY — identical to running `node -e` on a real Linux terminal
- `node -e <invalid>` must display the error (SyntaxError/ReferenceError) on stderr, not silently swallow it
- commands that only read stdin when stdin is a TTY (e.g. `tree`, `cat` with no args) must not hang when run from the shell; commands must detect whether stdin is a real data source vs an empty pipe/PTY
- Ctrl+C (SIGINT) must interrupt the foreground process group within 1 second, matching POSIX `isig` + `VINTR` behavior — this applies to all runtimes (WasmVM, Node, Python)
- signal delivery through the PTY line discipline → kernel process table → driver kill() chain must be end-to-end tested
- when adding or fixing process/signal/PTY behavior, always verify against the equivalent behavior on a real Linux system

## Compatibility Project-Matrix Policy

- compatibility fixtures live under `packages/secure-exec/tests/projects/` and MUST be black-box Node projects (`package.json` + source entrypoint)
Expand All @@ -63,6 +73,13 @@
- the matrix runs each fixture in host Node and secure-exec and compares normalized `code`, `stdout`, and `stderr`
- no known-mismatch classification is allowed; parity mismatches stay failing until runtime/bridge behavior is fixed

## Tested Package Tracking

- the Tested Packages section in `docs/nodejs-compatibility.mdx` lists all packages validated via the project-matrix test suite
- when adding a new project-matrix fixture, add the package to the Tested Packages table
- when removing a fixture, remove the package from the table
- the table links to GitHub Issues for requesting new packages to be tracked

## Test Structure

- `tests/test-suite/{node,python}.test.ts` are integration suite drivers; `tests/test-suite/{node,python}/` hold the shared suite definitions
Expand Down Expand Up @@ -97,6 +114,8 @@ Follow the style in `packages/secure-exec/src/index.ts`.
- `docs/runtimes/python.mdx` — update when PythonRuntime options/behavior changes
- `docs/system-drivers/node.mdx` — update when createNodeDriver options change
- `docs/system-drivers/browser.mdx` — update when createBrowserDriver options change
- `docs/nodejs-compatibility.mdx` — update when bridge, polyfill, or stub implementations change; keep the Tested Packages section current when adding or removing project-matrix fixtures
- `docs/cloudflare-workers-comparison.mdx` — update when secure-exec capabilities change; bump "Last updated" date

## Backlog Tracking

Expand Down
18 changes: 9 additions & 9 deletions docs/comparison/cloudflare-workers.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Node.js API compatibility comparison between secure-exec and Cloudf
icon: "scale-balanced"
---

*Last updated: 2026-03-10*
*Last updated: 2026-03-18*

## Overview

Expand Down Expand Up @@ -40,8 +40,8 @@ All three CF deployment models share the same `nodejs_compat` API surface. WfP a

| Module | secure-exec | CF Workers (`nodejs_compat`) | Notes |
| --- | --- | --- | --- |
| **`fs`** | 🟡 Core I/O: `readFile`, `writeFile`, `appendFile`, `open`, `read`, `write`, `close`, `readdir`, `mkdir`, `rmdir`, `rm`, `unlink`, `stat`, `lstat`, `rename`, `copyFile`, `exists`, `createReadStream`, `createWriteStream`, `writev`, `access`, `realpath`. Missing: `cp`, `glob`, `opendir`, `mkdtemp`, `statfs`, `readv`, `fdatasync`, `fsync`. Deferred: `watch`, `watchFile`, `chmod`, `chown`, `link`, `symlink`, `readlink`, `truncate`, `utimes`. Full coverage planned. | 🟡 In-memory VFS only. `/bundle` (read-only), `/tmp` (writable, ephemeral per-request), `/dev` devices. Missing: `watch`, `watchFile`, `globSync`, file permissions/ownership. All operations synchronous regardless of API style. Timestamps frozen to Unix epoch. 128 MB max file size. | **secure-exec**: Permission-gated; filesystem behavior determined by system driver (host FS or VFS). Read-only `/app/node_modules` overlay. **CF**: No persistent storage; `/tmp` contents isolated per request and lost after response; no real permissions or ownership. |
| **`http`** | 🟡 `request`, `get`, `createServer` with bridged request/response classes. Fetch-based, fully buffered. No connection pooling, no keep-alive tuning, no WebSocket upgrade, no trailer headers. `Agent` is stub-only. | 🟡 `request`, `get`, `createServer` via fetch API wrapper. Requires extra compat flags. No `Connection` headers, no `Expect: 100-continue`, no socket-level events (`socket`, `upgrade`), no 1xx responses, no trailer headers. `Agent` is stub-only. | |
| **`fs`** | 🟢 Core I/O: `readFile`, `writeFile`, `appendFile`, `open`, `read`, `write`, `close`, `readdir`, `mkdir`, `rmdir`, `rm`, `unlink`, `stat`, `lstat`, `rename`, `copyFile`, `exists`, `createReadStream`, `createWriteStream`, `writev`, `access`, `realpath`, `cp`, `glob`, `opendir`, `mkdtemp`, `statfs`, `readv`, `fdatasync`, `fsync`, `chmod`, `chown`, `link`, `symlink`, `readlink`, `truncate`, `utimes`. Deferred: `watch`, `watchFile`. | 🟡 In-memory VFS only. `/bundle` (read-only), `/tmp` (writable, ephemeral per-request), `/dev` devices. Missing: `watch`, `watchFile`, `globSync`, file permissions/ownership. All operations synchronous regardless of API style. Timestamps frozen to Unix epoch. 128 MB max file size. | **secure-exec**: Permission-gated; filesystem behavior determined by system driver (host FS or VFS). Read-only `/app/node_modules` overlay. **CF**: No persistent storage; `/tmp` contents isolated per request and lost after response; no real permissions or ownership. |
| **`http`** | 🟡 `request`, `get`, `createServer` with bridged request/response classes. Fetch-based, fully buffered. `Agent` with connection pooling and per-host `maxSockets` limits. HTTP upgrade (101 Switching Protocols) support. Trailer header support on `IncomingMessage`. No keep-alive tuning, no WebSocket data framing. | 🟡 `request`, `get`, `createServer` via fetch API wrapper. Requires extra compat flags. No `Connection` headers, no `Expect: 100-continue`, no socket-level events (`socket`, `upgrade`), no 1xx responses, no trailer headers. `Agent` is stub-only. | |
| **`https`** | 🟡 Same contract and limitations as `http`. | 🟡 Same wrapper model and limitations as `http`. | |
| **`http2`** | 🔴 Compatibility classes only; `createServer`/`createSecureServer` throw. | 🔴 Non-functional stub. | Neither platform supports HTTP/2 server creation. |
| **`net`** | 🔵 Planned. | 🟡 `net.connect()` / `net.Socket` for outbound TCP via Cloudflare Sockets API. No `net.createServer()`. | **CF**: Outbound TCP connections supported. **secure-exec**: On roadmap. |
Expand All @@ -56,7 +56,7 @@ All three CF deployment models share the same `nodejs_compat` API surface. WfP a
| **`process`** | 🟢 `env` (permission-gated), `cwd`/`chdir`, `exit`, timers, stdio event emitters, `hrtime`, `platform`, `arch`, `version`, `argv`, `pid`, `ppid`, `uid`, `gid`. | 🟡 `env`, `cwd`/`chdir`, `exit`, `nextTick`, `stdin`/`stdout`/`stderr`, `platform`, `arch`, `version`. No real process IDs or OS-level user/group IDs. Requires extra `enable_nodejs_process_v2` flag for full surface. | **secure-exec**: Configurable timing mitigation (`freeze` mode); real `pid`/`uid`/`gid` metadata. **CF**: Synthetic process metadata. |
| **`child_process`** | 🟢 `spawn`, `spawnSync`, `exec`, `execSync`, `execFile`, `execFileSync`. `fork` unsupported. | 🔴 Non-functional stub; all methods throw. | **secure-exec**: Bound to the system driver; subprocess behavior determined by driver implementation. CF has no subprocess support. |
| **`os`** | 🟢 `platform`, `arch`, `type`, `release`, `version`, `homedir`, `tmpdir`, `hostname`, `userInfo`, `os.constants`. | 🟡 Basic platform/arch metadata. | **secure-exec**: Richer OS metadata surface. |
| **`worker_threads`** | ⛔ Stubs that throw on API call. | 🔴 Non-functional stub. | Neither platform supports worker threads. |
| **`worker_threads`** | 🔴 Requireable; all APIs throw deterministic unsupported errors. | 🔴 Non-functional stub. | Neither platform supports worker threads. |
| **`cluster`** | ⛔ `require()` throws. | 🔴 Non-functional stub. | Neither platform supports clustering. |
| **`timers`** | 🟢 `setTimeout`, `clearTimeout`, `setInterval`, `clearInterval`, `setImmediate`, `clearImmediate`. | 🟢 Same surface; returns `Timeout` objects. | Equivalent support. |
| **`vm`** | 🔴 Browser polyfill via `Function()`/`eval()`. No real context isolation; shares global scope. | 🔴 Non-functional stub. | Neither offers real `vm` sandboxing. secure-exec polyfill silently runs code in shared scope, not safe for isolation. |
Expand Down Expand Up @@ -91,13 +91,13 @@ All three CF deployment models share the same `nodejs_compat` API surface. WfP a
| **`events`** | 🟢 Supported. | 🟢 Supported. | |
| **`module`** | 🟢 `createRequire`, `Module` basics, builtin resolution. | 🟡 Limited surface. | **secure-exec**: CJS/ESM with `createRequire`. |
| **`console`** | 🟢 Circular-safe bounded formatting; drop-by-default with `onStdio` hook. | 🟢 Supported; output routed to Workers Logs / Tail Workers. | |
| **`async_hooks`** | ⚪ TBD. | 🔴 Non-functional stub. | |
| **`perf_hooks`** | ⚪ TBD. | 🟡 Limited surface. | |
| **`diagnostics_channel`** | ⚪ TBD. | 🟢 Supported. | |
| **`readline`** | ⚪ TBD. | 🔴 Non-functional stub. | |
| **`async_hooks`** | 🔴 Stub: `AsyncLocalStorage` (run/enterWith/getStore/disable/exit), `AsyncResource` (runInAsyncScope/emitDestroy), `createHook` (returns enable/disable no-ops), `executionAsyncId`/`triggerAsyncId`. All methods are callable but do not track real async context. | 🔴 Non-functional stub. | |
| **`perf_hooks`** | 🔴 Requireable stub; APIs throw deterministic unsupported errors. | 🟡 Limited surface. | |
| **`diagnostics_channel`** | 🔴 Stub: `channel()`, `hasSubscribers()`, `tracingChannel()`, `Channel` constructor. All channels report no subscribers; `publish` is a no-op. Sufficient for framework compatibility (e.g., Fastify). | 🟢 Supported. | |
| **`readline`** | 🔴 Requireable stub; APIs throw deterministic unsupported errors. | 🔴 Non-functional stub. | |
| **`tty`** | 🔴 `isatty()` returns `false`; `ReadStream`/`WriteStream` throw. | 🔴 Stub-like. | Both platforms are essentially non-functional beyond `isatty()`. |
| **`constants`** | 🟢 Supported. | 🟢 Supported. | |
| **`punycode`** | Not listed. | 🟢 Supported (deprecated). | |
| **`punycode`** | 🟢 Supported via `node-stdlib-browser` polyfill (deprecated upstream). | 🟢 Supported (deprecated). | |

### Unsupported in Both

Expand Down
23 changes: 21 additions & 2 deletions docs/nodejs-compatibility.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Unsupported modules use: `"<module> is not supported in sandbox"`.
| `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` | 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. |
Expand All @@ -53,7 +53,8 @@ Unsupported modules use: `"<module> is not supported in sandbox"`.
| `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`. |
| `diagnostics_channel` | 3 (Stub) | No-op `channel()` and `tracingChannel()` stubs; channels always report `hasSubscribers: false`; `publish`, `subscribe`, `unsubscribe` are no-ops. Provides Fastify compatibility. |
| `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. |

Expand All @@ -69,8 +70,25 @@ The [project-matrix test suite](https://github.com/rivet-dev/secure-exec/tree/ma
| [vite](https://npmjs.com/package/vite) | Build Tool | ESM, HMR server, plugin system |
| [astro](https://npmjs.com/package/astro) | Web Framework | Island architecture, SSR, multi-framework |
| [hono](https://npmjs.com/package/hono) | Web Framework | ESM imports, lightweight HTTP |
| [axios](https://npmjs.com/package/axios) | HTTP Client | HTTP client requests via fetch adapter, JSON APIs |
| [node-fetch](https://npmjs.com/package/node-fetch) | HTTP Client | Fetch polyfill using http module, stream piping |
| [dotenv](https://npmjs.com/package/dotenv) | Configuration | Environment variable loading, fs reads |
| [semver](https://npmjs.com/package/semver) | Utility | Version parsing and comparison |
| [ssh2](https://npmjs.com/package/ssh2) | Networking | SSH client/server, crypto, streams, events |
| [ssh2-sftp-client](https://npmjs.com/package/ssh2-sftp-client) | Networking | SFTP client, file transfer APIs over SSH |
| [pg](https://npmjs.com/package/pg) | Database | PostgreSQL client, Pool/Client classes, type parsers |
| [mysql2](https://npmjs.com/package/mysql2) | Database | MySQL client, connection/pool classes, escape/format utilities |
| [ioredis](https://npmjs.com/package/ioredis) | Database | Redis client, Cluster, Command, pipeline/multi transaction APIs |
| [drizzle-orm](https://npmjs.com/package/drizzle-orm) | Database | ORM schema definition, query building, ESM module graph |
| [ws](https://npmjs.com/package/ws) | Networking | WebSocket client/server, HTTP upgrade, events |
| [jsonwebtoken](https://npmjs.com/package/jsonwebtoken) | Crypto | JWT signing (HS256), verification, decode |
| [bcryptjs](https://npmjs.com/package/bcryptjs) | Crypto | Pure JS password hashing and verification |
| [chalk](https://npmjs.com/package/chalk) | Terminal | Terminal string styling, ANSI escape codes |
| [lodash-es](https://npmjs.com/package/lodash-es) | Utility | Large ESM module resolution at scale |
| [pino](https://npmjs.com/package/pino) | Logging | Structured JSON logging, child loggers, serializers |
| [uuid](https://npmjs.com/package/uuid) | Crypto | UUID generation (v4, v5), validation, version detection |
| [yaml](https://npmjs.com/package/yaml) | Utility | YAML parsing, stringifying, document API |
| [zod](https://npmjs.com/package/zod) | Validation | Schema definition, parsing, safe parse, transforms |
| [rivetkit](https://npmjs.com/package/rivetkit) | SDK | Local vendor package resolution |
| crypto (builtin) | Crypto | `crypto.randomBytes`, `randomUUID`, `getRandomValues` |
| fs-metadata-rename | Filesystem | `stat` metadata, `rename` semantics |
Expand All @@ -85,6 +103,7 @@ The [project-matrix test suite](https://github.com/rivet-dev/secure-exec/tree/ma
| 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 |

To request a new package be added to the test suite, [open an issue](https://github.com/rivet-dev/secure-exec/issues/new?labels=package-request&title=Package+request:+%5Bpackage-name%5D).
Expand Down
36 changes: 34 additions & 2 deletions packages/kernel/src/kernel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -427,9 +427,13 @@ class KernelImpl implements Kernel {
// Resolve output callbacks: when a child inherits non-piped stdio from
// a parent, forward output to the parent's DriverProcess callbacks so
// cross-runtime child output reaches the top-level collector.
// When piped, wire a callback that forwards through the pipe/PTY so
// drivers that emit output via callbacks (Node) reach the PTY/pipe.
let stdoutCb: ((data: Uint8Array) => void) | undefined;
let stderrCb: ((data: Uint8Array) => void) | undefined;
if (!stdoutPiped) {
if (stdoutPiped) {
stdoutCb = this.createPipedOutputCallback(table, 1);
} else {
if (options?.onStdout) {
stdoutCb = options.onStdout;
} else if (callerPid !== undefined) {
Expand All @@ -440,7 +444,9 @@ class KernelImpl implements Kernel {
}
if (!stdoutCb) stdoutCb = (data) => stdoutBuf.push(data);
}
if (!stderrPiped) {
if (stderrPiped) {
stderrCb = this.createPipedOutputCallback(table, 2);
} else {
if (options?.onStderr) {
stderrCb = options.onStderr;
} else if (callerPid !== undefined) {
Expand Down Expand Up @@ -983,6 +989,32 @@ class KernelImpl implements Kernel {
return this.pipeManager.isPipe(entry.description.id) || this.ptyManager.isPty(entry.description.id);
}

/**
* Create a callback that forwards data through a piped stdio FD.
* Needed for drivers (like Node) that emit output via callbacks rather
* than kernel FD writes (like WasmVM does via WASI fd_write).
*/
private createPipedOutputCallback(
table: ProcessFDTable,
fd: number,
): ((data: Uint8Array) => void) | undefined {
const entry = table.get(fd);
if (!entry) return undefined;

const descId = entry.description.id;
if (this.pipeManager.isPipe(descId)) {
return (data) => {
try { this.pipeManager.write(descId, data); } catch { /* pipe closed */ }
};
}
if (this.ptyManager.isPty(descId)) {
return (data) => {
try { this.ptyManager.write(descId, data); } catch { /* pty closed */ }
};
}
return undefined;
}

/** Clean up all FDs for a process, closing pipe/PTY ends when last reference drops. */
private cleanupProcessFDs(pid: number): void {
const table = this.fdTableManager.get(pid);
Expand Down
Loading
Loading