From 4ec1ccab8e79e00df277c3920fb560683a3fb8c2 Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Tue, 10 Mar 2026 18:17:27 -0700 Subject: [PATCH 1/3] docs: add Cloudflare Workers compatibility comparison matrix and API coverage gaps --- docs-internal/todo.md | 18 ++++ docs/cloudflare-workers-comparison.mdx | 142 +++++++++++++++++++++++++ docs/docs.json | 4 + 3 files changed, 164 insertions(+) create mode 100644 docs/cloudflare-workers-comparison.mdx diff --git a/docs-internal/todo.md b/docs-internal/todo.md index 516935d52..7ce34222c 100644 --- a/docs-internal/todo.md +++ b/docs-internal/todo.md @@ -102,6 +102,24 @@ - [ ] Fix HTTP server lifecycle leaks when executions time out or are disposed. - `packages/secure-exec/src/execution.ts`, `packages/secure-exec/src/index.ts`, `packages/secure-exec/src/node/driver.ts` +## API Coverage Gaps + +- [ ] Add missing `fs` APIs: `cp`, `cpSync`, `glob`, `globSync`, `opendir`, `mkdtemp`, `mkdtempSync`, `statfs`, `statfsSync`, `readv`, `readvSync`, `fdatasync`, `fdatasyncSync`, `fsync`, `fsyncSync`. + - `packages/secure-exec/src/bridge/fs.ts` + - Goal: full `node:fs` coverage for core file operations. + +- [ ] Implement deferred `fs` APIs: `chmod`, `chown`, `link`, `symlink`, `readlink`, `truncate`, `utimes`, `watch`, `watchFile`. + - `packages/secure-exec/src/bridge/fs.ts` + - Currently throw deterministic unsupported errors. + +- [ ] Add missing `http`/`https` APIs: connection pooling (`Agent`), keep-alive tuning, WebSocket upgrade, trailer headers, socket-level events. + - `packages/secure-exec/src/bridge/network.ts` + - Current implementation is fetch-based and fully buffered with no socket-level control. + +- [ ] Fix `v8.serialize`/`v8.deserialize` to use V8 structured serialization instead of `JSON.stringify`/`JSON.parse`. + - `packages/secure-exec/isolate-runtime/src/inject/bridge-initial-globals.ts` (~line 51) + - Bug: current implementation silently produces JSON instead of V8 binary format. Code depending on structured clone semantics (`Map`, `Set`, `RegExp`, circular refs) will get wrong results. + ## Performance & Correctness - [ ] Add `stat` and `exists` methods to `VirtualFileSystem` interface. diff --git a/docs/cloudflare-workers-comparison.mdx b/docs/cloudflare-workers-comparison.mdx new file mode 100644 index 000000000..e564edf37 --- /dev/null +++ b/docs/cloudflare-workers-comparison.mdx @@ -0,0 +1,142 @@ +--- +title: Cloudflare Workers Comparison +description: Node.js API compatibility comparison between secure-exec and Cloudflare Workers (standard, Workers for Platforms, dynamic dispatch). +--- + +*Last updated: 2026-03-10* + +## Overview + +This document compares Node.js API support across **secure-exec** and three Cloudflare Workers deployment models: + +| Platform | Description | +| --- | --- | +| **CF Workers** | Standard Cloudflare Workers with `nodejs_compat` flag and compatibility date ≥ 2024-09-23. | +| **CF Workers for Platforms** | Multi-tenant variant where platform operators deploy user Workers into dispatch namespaces. Same V8 runtime as standard Workers; additional isolation constraints. | +| **CF Dynamic Dispatch** | The routing Worker that invokes user Workers in a Workers for Platforms namespace. Runs as a standard Worker with dispatch namespace bindings. | + +All three CF deployment models share the same `nodejs_compat` API surface. WfP adds operational restrictions (no `caches.default`, no `request.cf` without trusted mode, no gradual deployments, operator-enforced CPU/subrequest limits, outbound Worker interception) but no Node.js API differences. + +--- + +## Support Tier Legend + +| Icon | Meaning | +| --- | --- | +| 🟢 | Supported — native or full implementation. | +| 🔵 | Planned — not yet implemented; on the roadmap. | +| 🟡 | Partial — functional with behavioral gaps or wrapper limitations. | +| ⚪ | TBD — under consideration; not yet committed. | +| 🔴 | Stub — requireable but most APIs throw on call. | +| ⛔ | Unsupported — not available; `require()` throws immediately. | + +--- + +## Module Compatibility Matrix + +### Core I/O and Networking + +| 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. | | +| **`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. | +| **`tls`** | 🔵 Planned. | 🟡 `tls.connect()` for outbound TLS via Sockets API. No `tls.createServer()`. | **CF**: Outbound TLS supported. **secure-exec**: On roadmap. | +| **`dns`** | 🟢 `lookup`, `resolve`, `resolve4`, `resolve6`, plus `dns.promises`. | 🟡 DNS over HTTPS via Cloudflare 1.1.1.1. `lookup`, `lookupService`, `resolve` (generic) throw "Not implemented". | **secure-exec**: Permission-gated real DNS. **CF**: DoH only; core methods missing. | +| **`dgram`** | ⛔ `require()` throws. | 🔴 Non-functional stub. | Neither platform supports UDP sockets. | + +### Process and Runtime + +| Module | secure-exec | CF Workers (`nodejs_compat`) | Notes | +| --- | --- | --- | --- | +| **`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. | +| **`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. | +| **`v8`** | 🔴 Mock heap stats; `serialize`/`deserialize` use JSON instead of V8 binary format (bug). | 🔴 Non-functional stub. | Neither exposes real V8 internals. secure-exec `v8.serialize` silently produces JSON — needs fix to use V8 structured serialization. | + +### Crypto and Security + +| Module | secure-exec | CF Workers (`nodejs_compat`) | Notes | +| --- | --- | --- | --- | +| **`crypto`** | 🔵 Planned. Currently: `getRandomValues()` and `randomUUID()` use host `node:crypto` secure randomness. `subtle.*` throws unsupported errors. | 🟢 Full `node:crypto` surface (hash, HMAC, cipher, sign, verify, key generation). No DSA/DH key pairs, no `ed448`/`x448`, no FIPS mode. | **CF**: Comprehensive crypto support. **secure-exec**: Secure randomness today; full crypto planned. | +| **Web Crypto** | 🔵 Planned. | 🟢 Available without `nodejs_compat`. | CF has native Web Crypto. | +| **Fetch globals** | 🟢 `fetch`, `Headers`, `Request`, `Response`. | 🟢 Supported. | | + +### Data and Encoding + +| Module | secure-exec | CF Workers (`nodejs_compat`) | Notes | +| --- | --- | --- | --- | +| **`buffer`** | 🟢 Supported. | 🟢 Supported. | | +| **`stream`** | 🟢 Supported. | 🟢 Supported. | | +| **`string_decoder`** | 🟢 Supported. | 🟢 Supported. | | +| **`zlib`** | 🟢 Supported. | 🟢 Supported; includes Brotli. | CF adds Brotli. | +| **`querystring`** | 🟢 Supported. | 🟢 Supported. | | + +### Utilities and Diagnostics + +| Module | secure-exec | CF Workers (`nodejs_compat`) | Notes | +| --- | --- | --- | --- | +| **`path`** | 🟢 Supported. | 🟢 Supported. | | +| **`url`** | 🟢 Supported. | 🟢 Supported. | | +| **`util`** | 🟢 Supported. | 🟢 Supported. | | +| **`assert`** | 🟢 Supported. | 🟢 Supported. | | +| **`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. | | +| **`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). | | + +### Unsupported in Both + +| Module | secure-exec | CF Workers | Notes | +| --- | --- | --- | --- | +| **`wasi`** | ⛔ Unsupported | ⛔ Unsupported | | +| **`inspector`** | ⛔ Unsupported | 🟡 Partial (Chrome DevTools) | CF has limited inspector via DevTools. | +| **`repl`** | ⛔ Unsupported | 🔴 Stub | | +| **`trace_events`** | ⛔ Unsupported | ⛔ Unsupported | | +| **`domain`** | ⛔ Unsupported | ⛔ Unsupported | | + +--- + +## Execution Model Comparison + +| Capability | secure-exec | CF Workers / WfP / Dynamic Dispatch | +| --- | --- | --- | +| **Isolation** | V8 isolate. | V8 isolate per Worker invocation. | +| **Permission model** | Deny-by-default for `fs`, `network`, `childProcess`, `env`. Fine-grained per-domain policies. | No granular permission model. WfP adds `request.cf` restriction and cache isolation. | +| **Memory limits** | Configurable `memoryLimit` (MB). | 128 MB per Worker (platform-managed). | +| **CPU time limits** | Configurable `cpuTimeLimitMs` with exit code 124. | 10ms (free) / 30s (paid) CPU time; WfP operators can set custom limits. | +| **Timing mitigation** | `freeze` mode (deterministic clocks) or `off` (real-time). | I/O-gated coarsening — `Date.now()` and `performance.now()` only advance after I/O to mitigate Spectre-class side channels. | +| **Module loading** | CJS + ESM with package.json `type` field semantics; `node_modules` overlay. | ES modules primary; CJS via `nodejs_compat`; no `node_modules` overlay (bundled at deploy). | +| **Subprocess execution** | Bound to the system driver; subprocess behavior determined by driver implementation. | Not available. | +| **Filesystem** | System-driver-determined: host filesystem (permission-gated) or virtual filesystem, depending on driver implementation. Read-only `/app/node_modules` overlay. | Ephemeral VFS only; Durable Objects for persistence. | +| **Payload limits** | Configurable size limits on sandbox-to-host transfers. | 128 MB script size; request body limits per plan. | +| **Logging** | Drop-by-default; explicit `onStdio` hook for streaming. | Routed to Workers Logs / Tail Workers. | + +--- + +## Sources + +- [Cloudflare Workers Node.js Compatibility Docs](https://developers.cloudflare.com/workers/runtime-apis/nodejs/) +- [A Year of Improving Node.js Compatibility (2025)](https://blog.cloudflare.com/nodejs-workers-2025/) +- [Cloudflare Workers node:http Docs](https://developers.cloudflare.com/workers/runtime-apis/nodejs/http/) +- [Cloudflare Workers node:fs Docs](https://developers.cloudflare.com/workers/runtime-apis/nodejs/fs/) +- [Cloudflare Workers node:crypto Docs](https://developers.cloudflare.com/workers/runtime-apis/nodejs/crypto/) +- [Cloudflare Workers Compatibility Flags](https://developers.cloudflare.com/workers/configuration/compatibility-flags/) +- [Cloudflare Workers Performance and Timers](https://developers.cloudflare.com/workers/runtime-apis/performance/) +- [Mitigating Spectre: Cloudflare Workers Security Model](https://blog.cloudflare.com/mitigating-spectre-and-other-security-threats-the-cloudflare-workers-security-model/) +- [Workers for Platforms Configuration](https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/get-started/configuration/) +- [Workers for Platforms Limits](https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/reference/limits/) +- [How Workers for Platforms Works](https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/how-workers-for-platforms-works/) +- [Dynamic Dispatch Worker](https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/configuration/dynamic-dispatch/) diff --git a/docs/docs.json b/docs/docs.json index e297cdd4d..2d41e2d7b 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -36,6 +36,10 @@ { "group": "Compatibility", "pages": ["node-compatability"] + }, + { + "group": "Comparisons", + "pages": ["cloudflare-workers-comparison"] } ] } From cdc06dcddd685cf8ee757676dcc938c730f7b87b Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Tue, 10 Mar 2026 21:30:57 -0700 Subject: [PATCH 2/3] fix(playground): use workspace dependency for secure-exec instead of relative dist path --- packages/playground/frontend/app.ts | 4 ++-- packages/playground/package.json | 3 +++ pnpm-lock.yaml | 4 ++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/playground/frontend/app.ts b/packages/playground/frontend/app.ts index da1c2b04b..38c1f4dc5 100644 --- a/packages/playground/frontend/app.ts +++ b/packages/playground/frontend/app.ts @@ -3,8 +3,8 @@ import { allowAll, createBrowserDriver, createBrowserRuntimeDriverFactory, -} from "../../secure-exec/dist/browser-runtime.js"; -import type { StdioChannel, StdioEvent } from "../../secure-exec/dist/browser-runtime.js"; +} from "secure-exec/browser"; +import type { StdioChannel, StdioEvent } from "secure-exec/browser"; type Language = "nodejs" | "python"; type TypeScriptApi = typeof import("typescript"); diff --git a/packages/playground/package.json b/packages/playground/package.json index 2553fc308..dc4bfc200 100644 --- a/packages/playground/package.json +++ b/packages/playground/package.json @@ -11,6 +11,9 @@ "setup-vendor": "tsx ./scripts/setup-vendor.ts", "test": "pnpm exec vitest run ./tests/" }, + "dependencies": { + "secure-exec": "workspace:*" + }, "devDependencies": { "monaco-editor": "0.52.2", "pyodide": "0.28.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 83ea7fdad..78e4500eb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -63,6 +63,10 @@ importers: version: 4.21.0 packages/playground: + dependencies: + secure-exec: + specifier: workspace:* + version: link:../secure-exec devDependencies: monaco-editor: specifier: 0.52.2 From e768a5cbce027e266b451816e095ed764a15e209 Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Tue, 10 Mar 2026 21:39:48 -0700 Subject: [PATCH 3/3] fix(typescript): increase timeout for node types typecheck test --- .../tests/typescript-tools.integration.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/secure-exec-typescript/tests/typescript-tools.integration.test.ts b/packages/secure-exec-typescript/tests/typescript-tools.integration.test.ts index f5bfa1bc1..5fb20181d 100644 --- a/packages/secure-exec-typescript/tests/typescript-tools.integration.test.ts +++ b/packages/secure-exec-typescript/tests/typescript-tools.integration.test.ts @@ -29,7 +29,7 @@ function createTools(memoryLimit?: number) { } describe("@secure-exec/typescript", () => { - it("typechecks a project with node types from node_modules", async () => { + it("typechecks a project with node types from node_modules", { timeout: 15_000 }, async () => { const { filesystem, tools } = createTools(); await filesystem.mkdir("/root/src"); await filesystem.writeFile(