Where: packages/safe-bash/src/contracts/io.ts:5 (import { TransformStream } from "node:stream/web") and packages/safe-bash/src/contracts/path.ts:2 (export { posix as posixPath } from "node:path"). Both are in the contracts/ core layer, which is in the import graph of the @poe-platform/safe-bash/browser entry. The ./browser export has no browser/workerd condition (package.json: "./browser": { "import": "./dist/browser.js" }), so these node: specifiers ship verbatim in dist/contracts/io.js and dist/contracts/path.js.
Contradicts the README (README.md, "Browsers and Workers"): "Use @poe-platform/safe-bash/browser … No nodejs_compat flag or Node globals are required."
PoC: bundle the documented browser entry for workerd and deploy with compatibility_flags = []:
esbuild worker.ts --bundle --format=esm --platform=browser --conditions=workerd,worker,browser
# worker.ts imports only from @poe-platform/safe-bash/browser (createBrowserCommands, portableSearchCommands, Shell, createMemoryFileSystem)
wrangler dev --local # compatibility_flags = []
Measured (workerd via wrangler dev --local, wrangler 4.129.1): the runtime fails to start:
service core:user:safebash-browser: Uncaught Error: No such module "node:stream/web".
imported from "worker-browser.js"
The Workers runtime failed to start.
esbuild import trace confirms the source:
node:stream/web <= packages/safe-bash/src/contracts/io.ts
node:path <= packages/safe-bash/src/contracts/path.ts
The same imports are present in the built artifact: dist/contracts/io.js:3 and dist/contracts/path.js:2. Adding compatibility_flags = ["nodejs_compat"] makes the identical bundle start and run (grep, sed, pipelines work) — so the only blocker is the two node: imports the docs say are not needed.
Impact: (d) availability, with a (a) secondary — the documented, secret-safe Workers deployment path does not run at all. The available workaround is to enable nodejs_compat, which is exactly what maps a Worker's secret bindings into process.env (the risk the same README warns about in the env row). So the broken no-compat path pushes operators onto the higher-secret-exposure configuration.
Fix: in contracts/io.ts use the global TransformStream (a standard global in workerd and browsers) instead of importing it from node:stream/web; in contracts/path.ts use a portable posix-path join/normalize (safe-fs already ships one) instead of node:path. Alternatively add a browser/workerd condition on the ./browser export (and the contracts subpaths) that resolves to node-free implementations, and add a CI check that bundles the browser entry with --conditions=workerd and asserts zero node: specifiers.
Found in security audit v3, Cloudflare Workers pass (2026-09-08), verified on real workerd (wrangler dev --local).
Where:
packages/safe-bash/src/contracts/io.ts:5(import { TransformStream } from "node:stream/web") andpackages/safe-bash/src/contracts/path.ts:2(export { posix as posixPath } from "node:path"). Both are in thecontracts/core layer, which is in the import graph of the@poe-platform/safe-bash/browserentry. The./browserexport has nobrowser/workerdcondition (package.json:"./browser": { "import": "./dist/browser.js" }), so thesenode:specifiers ship verbatim indist/contracts/io.jsanddist/contracts/path.js.Contradicts the README (
README.md, "Browsers and Workers"): "Use@poe-platform/safe-bash/browser… Nonodejs_compatflag or Node globals are required."PoC: bundle the documented browser entry for workerd and deploy with
compatibility_flags = []:Measured (workerd via
wrangler dev --local,wrangler4.129.1): the runtime fails to start:esbuild import trace confirms the source:
The same imports are present in the built artifact:
dist/contracts/io.js:3anddist/contracts/path.js:2. Addingcompatibility_flags = ["nodejs_compat"]makes the identical bundle start and run (grep,sed, pipelines work) — so the only blocker is the twonode:imports the docs say are not needed.Impact: (d) availability, with a (a) secondary — the documented, secret-safe Workers deployment path does not run at all. The available workaround is to enable
nodejs_compat, which is exactly what maps a Worker's secret bindings intoprocess.env(the risk the same README warns about in theenvrow). So the broken no-compat path pushes operators onto the higher-secret-exposure configuration.Fix: in
contracts/io.tsuse the globalTransformStream(a standard global in workerd and browsers) instead of importing it fromnode:stream/web; incontracts/path.tsuse a portable posix-path join/normalize (safe-fs already ships one) instead ofnode:path. Alternatively add abrowser/workerdcondition on the./browserexport (and thecontractssubpaths) that resolves to node-free implementations, and add a CI check that bundles the browser entry with--conditions=workerdand asserts zeronode:specifiers.Found in security audit v3, Cloudflare Workers pass (2026-09-08), verified on real workerd (
wrangler dev --local).