diff --git a/.changeset/just-bash-3-6.md b/.changeset/just-bash-3-6.md new file mode 100644 index 000000000..b9c85e4e7 --- /dev/null +++ b/.changeset/just-bash-3-6.md @@ -0,0 +1,5 @@ +--- +"@cloudflare/computer": patch +--- + +Update `just-bash` to 3.6. The Worker shell keeps running `curl` on plain `fetch`, without `guarded-fetch`. See [Worker backend](https://github.com/cloudflare/computer/blob/main/docs/12_worker_backend.md). diff --git a/examples/artifacts/package.json b/examples/artifacts/package.json index 6c4c6a8f6..458aa2d0b 100644 --- a/examples/artifacts/package.json +++ b/examples/artifacts/package.json @@ -12,7 +12,7 @@ }, "dependencies": { "@cloudflare/computer": "*", - "just-bash": "^3.0.1" + "just-bash": "^3.6.0" }, "devDependencies": { "typescript": "^6.0.3", diff --git a/examples/egress/package.json b/examples/egress/package.json index 08e73ab24..e3cf6192f 100644 --- a/examples/egress/package.json +++ b/examples/egress/package.json @@ -13,7 +13,7 @@ }, "dependencies": { "@cloudflare/computer": "*", - "just-bash": "^3.0.1" + "just-bash": "^3.6.0" }, "devDependencies": { "typescript": "^6.0.3", diff --git a/examples/mcp/package.json b/examples/mcp/package.json index afc44bb20..ea7fd0331 100644 --- a/examples/mcp/package.json +++ b/examples/mcp/package.json @@ -18,7 +18,7 @@ "@modelcontextprotocol/sdk": "1.32.1", "@platformatic/vfs": "^0.4.0", "ai": "^7.0.0", - "just-bash": "^3.0.1", + "just-bash": "^3.6.0", "zod": "^4.4.3" }, "devDependencies": { diff --git a/examples/pi-ai/package.json b/examples/pi-ai/package.json index 984614495..533998a7f 100644 --- a/examples/pi-ai/package.json +++ b/examples/pi-ai/package.json @@ -14,7 +14,7 @@ "dependencies": { "@cloudflare/computer": "*", "@earendil-works/pi-ai": "^0.99.2", - "just-bash": "^3.0.1", + "just-bash": "^3.6.0", "zod": "^4.4.3" }, "devDependencies": { diff --git a/examples/tanstack-ai/package.json b/examples/tanstack-ai/package.json index bceac9d92..35ea3292e 100644 --- a/examples/tanstack-ai/package.json +++ b/examples/tanstack-ai/package.json @@ -15,7 +15,7 @@ "@cloudflare/computer": "*", "@tanstack/ai": "^0.63.0", "@tanstack/ai-cloudflare": "^0.2.1", - "just-bash": "^3.0.1", + "just-bash": "^3.6.0", "zod": "^4.4.3" }, "devDependencies": { diff --git a/examples/think-compare-runtimes/package.json b/examples/think-compare-runtimes/package.json index 389f1d3c3..509f2aef4 100644 --- a/examples/think-compare-runtimes/package.json +++ b/examples/think-compare-runtimes/package.json @@ -29,7 +29,7 @@ "@shikijs/themes": "^4.2.0", "agents": "^0.29.0", "ai": "^7.0.0", - "just-bash": "^3.0.1", + "just-bash": "^3.6.0", "partyserver": "^0.5.6", "partysocket": "^1.1.19", "react": "^19.2.7", diff --git a/examples/think/package.json b/examples/think/package.json index abe4d6900..d9cf4c7c0 100644 --- a/examples/think/package.json +++ b/examples/think/package.json @@ -26,7 +26,7 @@ "agents": "^0.29.0", "ai": "^7.0.0", "isomorphic-git": "^1.38.3", - "just-bash": "^3.0.1", + "just-bash": "^3.6.0", "workers-ai-provider": "^4.0.0", "zod": "^4.4.3" }, diff --git a/examples/vite-bundle/package.json b/examples/vite-bundle/package.json index bba6f9c75..22260b964 100644 --- a/examples/vite-bundle/package.json +++ b/examples/vite-bundle/package.json @@ -14,7 +14,7 @@ }, "dependencies": { "@cloudflare/computer": "*", - "just-bash": "^3.0.1" + "just-bash": "^3.6.0" }, "devDependencies": { "@cloudflare/vite-plugin": "^1.68.0", diff --git a/examples/worker-shell/package.json b/examples/worker-shell/package.json index afef852a5..aa282f9d4 100644 --- a/examples/worker-shell/package.json +++ b/examples/worker-shell/package.json @@ -14,7 +14,7 @@ }, "dependencies": { "@cloudflare/computer": "*", - "just-bash": "^3.0.1" + "just-bash": "^3.6.0" }, "devDependencies": { "typescript": "^6.0.3", diff --git a/package-lock.json b/package-lock.json index 15d03773a..63af92295 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,7 +33,7 @@ "version": "0.0.0", "dependencies": { "@cloudflare/computer": "*", - "just-bash": "^3.0.1" + "just-bash": "^3.6.0" }, "devDependencies": { "typescript": "^6.0.3", @@ -279,7 +279,7 @@ "version": "0.0.0", "dependencies": { "@cloudflare/computer": "*", - "just-bash": "^3.0.1" + "just-bash": "^3.6.0" }, "devDependencies": { "typescript": "^6.0.3", @@ -297,7 +297,7 @@ "@modelcontextprotocol/sdk": "1.32.1", "@platformatic/vfs": "^0.4.0", "ai": "^7.0.0", - "just-bash": "^3.0.1", + "just-bash": "^3.6.0", "zod": "^4.4.3" }, "devDependencies": { @@ -313,7 +313,7 @@ "dependencies": { "@cloudflare/computer": "*", "@earendil-works/pi-ai": "^0.99.2", - "just-bash": "^3.0.1", + "just-bash": "^3.6.0", "zod": "^4.4.3" }, "devDependencies": { @@ -537,7 +537,7 @@ "@cloudflare/computer": "*", "@tanstack/ai": "^0.63.0", "@tanstack/ai-cloudflare": "^0.2.1", - "just-bash": "^3.0.1", + "just-bash": "^3.6.0", "zod": "^4.4.3" }, "devDependencies": { @@ -561,7 +561,7 @@ "agents": "^0.29.0", "ai": "^7.0.0", "isomorphic-git": "^1.38.3", - "just-bash": "^3.0.1", + "just-bash": "^3.6.0", "workers-ai-provider": "^4.0.0", "zod": "^4.4.3" }, @@ -590,7 +590,7 @@ "@shikijs/themes": "^4.2.0", "agents": "^0.29.0", "ai": "^7.0.0", - "just-bash": "^3.0.1", + "just-bash": "^3.6.0", "partyserver": "^0.5.6", "partysocket": "^1.1.19", "react": "^19.2.7", @@ -1204,7 +1204,7 @@ "version": "0.0.0", "dependencies": { "@cloudflare/computer": "*", - "just-bash": "^3.0.1" + "just-bash": "^3.6.0" }, "devDependencies": { "@cloudflare/vite-plugin": "^1.68.0", @@ -1218,7 +1218,7 @@ "version": "0.0.0", "dependencies": { "@cloudflare/computer": "*", - "just-bash": "^3.0.1" + "just-bash": "^3.6.0" }, "devDependencies": { "typescript": "^6.0.3", @@ -4920,48 +4920,6 @@ "node": ">=18.0.0" } }, - "node_modules/@jitl/quickjs-ffi-types": { - "version": "0.32.0", - "resolved": "https://registry.npmjs.org/@jitl/quickjs-ffi-types/-/quickjs-ffi-types-0.32.0.tgz", - "integrity": "sha512-v9T+GQpmk43VDJ7d72sf0Nexhk+ArvtUihW27dy7lqAl0zBObFKtSBBIm5RBjwIhE8VwsPPm9PNuvPvNqLWUEg==", - "license": "MIT" - }, - "node_modules/@jitl/quickjs-wasmfile-debug-asyncify": { - "version": "0.32.0", - "resolved": "https://registry.npmjs.org/@jitl/quickjs-wasmfile-debug-asyncify/-/quickjs-wasmfile-debug-asyncify-0.32.0.tgz", - "integrity": "sha512-EX8zbXwGqCgAE764M+qvkHtyXDi/FUoMBea0JnES7vCM3P7a2+EOZOjGv85wtZ2sJhI1oJ+nekmqpOODFDY+hw==", - "license": "MIT", - "dependencies": { - "@jitl/quickjs-ffi-types": "0.32.0" - } - }, - "node_modules/@jitl/quickjs-wasmfile-debug-sync": { - "version": "0.32.0", - "resolved": "https://registry.npmjs.org/@jitl/quickjs-wasmfile-debug-sync/-/quickjs-wasmfile-debug-sync-0.32.0.tgz", - "integrity": "sha512-LeYWrPGC1uNCTBWvibo3ZLJj0CSVNYUXvJpXMCmuQ5Sap2cCACc3uvGvYV4homHHBAzfw5akoTqMMS4YFRtw+Q==", - "license": "MIT", - "dependencies": { - "@jitl/quickjs-ffi-types": "0.32.0" - } - }, - "node_modules/@jitl/quickjs-wasmfile-release-asyncify": { - "version": "0.32.0", - "resolved": "https://registry.npmjs.org/@jitl/quickjs-wasmfile-release-asyncify/-/quickjs-wasmfile-release-asyncify-0.32.0.tgz", - "integrity": "sha512-3oSwPfja12ICz4aIblB58cuY8JlEq5Txt8Cut4VLo+LH47QN+mzCnSgnbB03hWzg1LBcc+VyyI9UOag7a1NF+Q==", - "license": "MIT", - "dependencies": { - "@jitl/quickjs-ffi-types": "0.32.0" - } - }, - "node_modules/@jitl/quickjs-wasmfile-release-sync": { - "version": "0.32.0", - "resolved": "https://registry.npmjs.org/@jitl/quickjs-wasmfile-release-sync/-/quickjs-wasmfile-release-sync-0.32.0.tgz", - "integrity": "sha512-BKNDI/TPBfGlLNGYpLrhcDGXmIk4xHm4MRAisOBnOzpXVn9HZWsfmMAc9WMBrAHjvvds6HOikKeaOBKdPdpVrg==", - "license": "MIT", - "dependencies": { - "@jitl/quickjs-ffi-types": "0.32.0" - } - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -8811,15 +8769,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -8987,18 +8936,6 @@ "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", "license": "MIT" }, - "node_modules/brace-expansion": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", - "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "20 || >=22" - } - }, "node_modules/braces": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", @@ -11175,6 +11112,28 @@ "dev": true, "license": "ISC" }, + "node_modules/guarded-fetch": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/guarded-fetch/-/guarded-fetch-0.1.3.tgz", + "integrity": "sha512-PSaNyJk8VJlUVEhMHPXT/Z/WgNrLBh43WaYQJiu1qvyCuPQA+kgFMNmAOxI3GU3uXrCkO/xRW11XjcswrksYOA==", + "license": "Apache-2.0", + "dependencies": { + "ipaddr.js": "2.2.0", + "undici": "7.29.0" + }, + "engines": { + "node": ">=20.19" + } + }, + "node_modules/guarded-fetch/node_modules/ipaddr.js": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/has-bigints": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", @@ -12324,38 +12283,38 @@ } }, "node_modules/just-bash": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/just-bash/-/just-bash-3.2.0.tgz", - "integrity": "sha512-hRTLLWBXCKuosjaNFJR7uPYBza+T2vjG3NdPBz4wxlctlnxwrbLjtLQf6RtSKmy/jzNJ6/URCtvxrXjy6yFGeQ==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/just-bash/-/just-bash-3.6.0.tgz", + "integrity": "sha512-2COjEgcHdqKXeQSiNfPAHmpwQx4S8wATaIwog83g2t645U5tom6FED4CBBdr3M+0ioX4xZ26eODw3/nD6RQ6xw==", "license": "Apache-2.0", "dependencies": { - "diff": "^8.0.2", - "fast-xml-parser": "^5.7.3", - "file-type": "^21.2.0", + "diff": "^8.0.4", + "fast-xml-parser": "^5.10.1", + "file-type": "^21.3.4", + "guarded-fetch": "0.1.3", "ini": "^6.0.0", - "minimatch": "^10.1.1", - "modern-tar": "^0.7.3", - "papaparse": "^5.5.3", - "quickjs-emscripten": "^0.32.0", - "re2js": "^1.2.1", + "modern-tar": "^0.7.7", + "papaparse": "^5.6.0", + "re2js": "^1.3.3", + "run": "2.1.3", "seek-bzip": "^2.0.0", - "smol-toml": "^1.6.0", + "smol-toml": "^1.9.0", "sprintf-js": "^1.1.3", - "sql.js": "^1.13.0", - "turndown": "^7.2.2", - "undici": "^7.25.0", - "yaml": "^2.8.2" + "sql.js": "^1.14.1", + "turndown": "^7.2.4", + "typescript": "^5.9.3", + "yaml": "^2.9.0" }, "bin": { "just-bash": "dist/bin/just-bash.js", "just-bash-shell": "dist/bin/shell/shell.js" }, "engines": { - "node": ">=20.18.1" + "node": ">=20.19" }, "optionalDependencies": { "@mongodb-js/zstd": "^7.0.0", - "node-liblzma": "^2.0.3" + "node-liblzma": "^2.2.0" } }, "node_modules/just-bash/node_modules/diff": { @@ -12367,6 +12326,19 @@ "node": ">=0.3.1" } }, + "node_modules/just-bash/node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/jwa": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", @@ -14437,21 +14409,6 @@ } } }, - "node_modules/minimatch": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", - "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.8" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", @@ -15258,9 +15215,9 @@ "license": "(MIT AND Zlib)" }, "node_modules/papaparse": { - "version": "5.5.4", - "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.5.4.tgz", - "integrity": "sha512-SwzWD9gl/ElwYLCI0nUja1mFJzjq2D8ziShfNBa7zCHzkOozeOGDwHWQ+tvCzEZcewecWZ5U7kUopDnG+DFYEQ==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.7.0.tgz", + "integrity": "sha512-qBGxg/7Q3Kl9Wfhrz2Z74UnvnHTXLNG6jmKJFeBvP2+y4lV7So+7SR62+Zd47JvdrCkX+nDcnr0ObPzek/+6RA==", "license": "MIT" }, "node_modules/parse-entities": { @@ -15796,31 +15753,6 @@ ], "license": "MIT" }, - "node_modules/quickjs-emscripten": { - "version": "0.32.0", - "resolved": "https://registry.npmjs.org/quickjs-emscripten/-/quickjs-emscripten-0.32.0.tgz", - "integrity": "sha512-So0Sqw869y/S2oE3Nuc0uT3Dhqgvsj8FSrwBdsuTosVsG8ME5/OcudU1GxsrIFdFABgy17GHnTVO9TYV/bLQcA==", - "license": "MIT", - "dependencies": { - "@jitl/quickjs-wasmfile-debug-asyncify": "0.32.0", - "@jitl/quickjs-wasmfile-debug-sync": "0.32.0", - "@jitl/quickjs-wasmfile-release-asyncify": "0.32.0", - "@jitl/quickjs-wasmfile-release-sync": "0.32.0", - "quickjs-emscripten-core": "0.32.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/quickjs-emscripten-core": { - "version": "0.32.0", - "resolved": "https://registry.npmjs.org/quickjs-emscripten-core/-/quickjs-emscripten-core-0.32.0.tgz", - "integrity": "sha512-QFnPfjFey8EqknSrSxe1hZrf1/8z7/6s1QzGOmKo6++02r7QRRX7ZoyNaZh7JuVjWsVW87KnQrbZqnHkOAzUyg==", - "license": "MIT", - "dependencies": { - "@jitl/quickjs-ffi-types": "0.32.0" - } - }, "node_modules/range-parser": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", @@ -16314,6 +16246,23 @@ "node": ">= 18" } }, + "node_modules/run": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/run/-/run-2.1.3.tgz", + "integrity": "sha512-KUurW05zgjtZ2su0cBURC1q2RG/Uq0uzV3qURDsXqiKh03p9IXABQK4Qem56cFMqjT8TdQ2iTWsePrMprQWIRA==", + "license": "Apache-2.0", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "typescript": ">=5.1.6 <7" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -18208,7 +18157,7 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -20743,7 +20692,7 @@ "esbuild": "^0.28.1", "ignore": "^7.0.14", "isomorphic-git": "^1.38.3", - "just-bash": "^3.0.1", + "just-bash": "^3.6.0", "memfs": "^4.57.6", "picomatch": "^4.0.7", "tsdown": "^0.23.1", @@ -20757,7 +20706,7 @@ "@cloudflare/workers-types": "^4.20260616.1 || ^5.20260921.1", "@platformatic/vfs": "*", "ai": "^6.0.196 || ^7.0.0", - "just-bash": "^3.0.1", + "just-bash": "^3.6.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "zod": "^4.4.3" }, diff --git a/packages/computer/package.json b/packages/computer/package.json index e0f596e81..6d9db2df5 100644 --- a/packages/computer/package.json +++ b/packages/computer/package.json @@ -180,7 +180,7 @@ "@cloudflare/workers-types": "^4.20260616.1 || ^5.20260921.1", "@platformatic/vfs": "*", "ai": "^6.0.196 || ^7.0.0", - "just-bash": "^3.0.1", + "just-bash": "^3.6.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "zod": "^4.4.3" }, @@ -221,7 +221,7 @@ "esbuild": "^0.28.1", "ignore": "^7.0.14", "isomorphic-git": "^1.38.3", - "just-bash": "^3.0.1", + "just-bash": "^3.6.0", "memfs": "^4.57.6", "picomatch": "^4.0.7", "tsdown": "^0.23.1", diff --git a/packages/computer/src/backends/worker-shell/script/build-bundle.mjs b/packages/computer/src/backends/worker-shell/script/build-bundle.mjs index 90503eac0..e31106cfc 100644 --- a/packages/computer/src/backends/worker-shell/script/build-bundle.mjs +++ b/packages/computer/src/backends/worker-shell/script/build-bundle.mjs @@ -34,14 +34,15 @@ // (script/undici-stub.mjs) because the Worker backend runs curl on // the plain-fetch path, never the undici DNS-pinning path — so a // consumer that imports the curl group gets a working curl without -// undici's bytes. +// undici's bytes. just-bash's guarded-fetch, which does that pinning +// through undici, is kept out the same way (see exclude-guarded-fetch). // // The generated/ output is gitignored. It's regenerated by the // computer package's prepare / pretest / pretypecheck scripts // so any install from this repo or downstream consumer sees a // fresh bundle matching the source. -import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"; +import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { basename, dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; @@ -147,6 +148,40 @@ try { "seek-bzip", ], plugins: [ + // just-bash (3.6 and later) starts `import("guarded-fetch")` when + // it loads and sends every curl request through it once it + // resolves. guarded-fetch adds DNS pinning and private-range + // checks, through undici and node:dns. The Worker backend runs + // with denyPrivateRanges off and leaves egress to the Dynamic + // Worker's globalOutbound, so guarded-fetch changes nothing for + // it, yet it would add about 42 KB to core, which every shell + // loads, curl or not. With the import resolving to null, just-bash + // takes its plain fetch path, as it does in a browser, through the + // SecureFetch entrypoint.ts passes. The import is in a minified + // file, so a just-bash without it fails the build here rather + // than quietly bundling guarded-fetch. + { + name: "exclude-guarded-fetch", + setup(pluginBuild) { + let replaced = 0; + pluginBuild.onLoad({ filter: /[/\\]just-bash[/\\]dist[/\\].*\.js$/ }, async (args) => { + const source = await readFile(args.path, "utf8"); + if (!source.includes('import("guarded-fetch")')) return undefined; + replaced += 1; + return { + contents: source.replaceAll('import("guarded-fetch")', "Promise.resolve(null)"), + loader: "js", + }; + }); + pluginBuild.onEnd((result) => { + if (result.errors.length === 0 && replaced === 0) { + throw new Error( + 'build-bundle: just-bash no longer has import("guarded-fetch"); check how curl reaches the network before updating exclude-guarded-fetch', + ); + } + }); + }, + }, // curl reaches undici only through just-bash's DNS-pinning // connection owner, which the Worker backend never activates: // ShellWorker registers curl on the plain-`fetch` path diff --git a/packages/computer/src/backends/worker-shell/shell-modules.test.ts b/packages/computer/src/backends/worker-shell/shell-modules.test.ts index 8b61c2d8a..42829830a 100644 --- a/packages/computer/src/backends/worker-shell/shell-modules.test.ts +++ b/packages/computer/src/backends/worker-shell/shell-modules.test.ts @@ -89,14 +89,30 @@ describe("SHELL_CORE_MODULES", () => { } }); - it("excludes the real undici dependency, leaving only the stub", () => { - // undici is redirected to a throwing stub at bundle time, so - // even when curl is included it runs on the fetch path and the - // ~620 KB real dependency never ships. The stub marker lives in - // core (the secure-fetch seam), so it is present by default. - const marker = "undici is excluded from the Worker shell bundle"; - const stubPresent = Object.values(SHELL_CORE_MODULES).some((mod) => mod.js.includes(marker)); - expect(stubPresent).toBe(true); + it("leaves out guarded-fetch and the real undici, in core and every group", () => { + // curl runs on the plain fetch path, so neither guarded-fetch's DNS + // pinning nor the ~620 KB undici it pins through ships. Each marker is + // a string literal of the real library's: undici's error code, and the + // message guarded-fetch blocks a URL with. + const groups = [ + SHELL_CORE_MODULES, + curlModules, + fileModules, + htmlToMarkdownModules, + jqModules, + jsExecModules, + pythonModules, + sqliteModules, + xanModules, + yqModules, + ]; + for (const marker of ["UND_ERR_CONNECT_TIMEOUT", "guarded-fetch blocked URL"]) { + for (const group of groups) { + for (const [name, mod] of Object.entries(group)) { + expect(mod.js.includes(marker), `${marker} in ${name}`).toBe(false); + } + } + } }); }); diff --git a/packages/computer/tests/worker-backend-worker.ts b/packages/computer/tests/worker-backend-worker.ts index 08b68873d..7d8411d52 100644 --- a/packages/computer/tests/worker-backend-worker.ts +++ b/packages/computer/tests/worker-backend-worker.ts @@ -25,6 +25,15 @@ import { Workspace } from "../src/index"; export { WorkspaceServiceProxy } from "../src/proxy"; +/** The gateway the networked shell's requests go to: it answers with what it was sent. */ +export class EchoGateway extends WorkerEntrypoint { + override async fetch(request: Request): Promise { + return new Response(`${request.method} ${request.url} ${await request.text()}`, { + headers: { "x-echo": "yes" }, + }); + } +} + export interface Env { HOST: DurableObjectNamespace; LOADER: WorkerLoader; @@ -47,6 +56,19 @@ export class HostDO extends DurableObject { // fetch-path curl integration test exercises the wiring. commands: [curlModules], }), + // The same shell with a network: its requests go to EchoGateway, so curl's whole path, + // through just-bash's fetch and the Dynamic Worker's outbound, runs without real egress. + new WorkerShellBackend({ + id: "worker-shell-net", + loader: env.LOADER, + workspace: { binding: "HOST", id: ctx.id.toString() }, + ctx, + commands: [curlModules], + egress: { + mode: "http-gateway", + gateway: (ctx as unknown as { exports: { EchoGateway: Fetcher } }).exports.EchoGateway, + }, + }), ], }); } @@ -81,7 +103,10 @@ export class HostDO extends DurableObject { return this.#workspace.fs.readFile(path, "utf8"); } - async exec(command: string): Promise<{ + async exec( + command: string, + backend?: string, + ): Promise<{ exitCode: number; stdout: string; stderr: string; @@ -90,6 +115,7 @@ export class HostDO extends DurableObject { await this.#seed(); const handle = await this.#workspace.runtime.exec(command, { encoding: "utf8", + ...(backend === undefined ? {} : { backend }), }); const result = await handle.result(); return { @@ -130,7 +156,7 @@ export default class extends WorkerEntrypoint { if (url.pathname === "/exec") { const command = url.searchParams.get("command") ?? "true"; - const result = await stub.exec(command); + const result = await stub.exec(command, url.searchParams.get("backend") ?? undefined); return Response.json(result); } diff --git a/packages/computer/tests/worker-backend.test.ts b/packages/computer/tests/worker-backend.test.ts index a0cc49e7c..2f8384f89 100644 --- a/packages/computer/tests/worker-backend.test.ts +++ b/packages/computer/tests/worker-backend.test.ts @@ -69,6 +69,7 @@ async function read(id: string, path: string): Promise { async function exec( id: string, command: string, + backend?: string, ): Promise<{ exitCode: number; stdout: string; @@ -78,6 +79,7 @@ async function exec( const url = new URL("http://test/exec"); url.searchParams.set("id", id); url.searchParams.set("command", command); + if (backend !== undefined) url.searchParams.set("backend", backend); const res = await SELF.fetch(url); if (!res.ok) throw new Error(`exec failed: ${res.status} ${await res.text()}`); return res.json(); @@ -165,6 +167,21 @@ describe("WorkerShellBackend end-to-end", () => { expect(result.stdout).not.toMatch(/command not found/); }); + it("fetches with curl through the Dynamic Worker's outbound", async () => { + // The networked shell sends its requests to EchoGateway, which answers with what it got, so + // this covers curl's whole path: just-bash's fetch, then the Dynamic Worker's outbound. + const id = freshId(); + const result = await exec( + id, + "curl -s -i -X POST -d hello https://api.example.test/things", + "worker-shell-net", + ); + expect(result.exitCode).toBe(0); + expect(result.stdout).toMatch(/^HTTP\/1\.1 200/); + expect(result.stdout.toLowerCase()).toContain("x-echo: yes"); + expect(result.stdout).toContain("POST https://api.example.test/things hello"); + }); + it("isolates state between separate workspace ids", async () => { // Two host-DO names → two distinct workspaces, two distinct // Dynamic Worker isolates (the loader caches by