Compare commits

...
Author SHA1 Message Date
jason.woltje e99c6726a1 build(pi): prepare pinned offline Undici rebuild recipe (#713) 2026-10-29 01:00:26 -05:00
11 changed files with 1599 additions and 0 deletions

No files matched your search

+6
View File
@@ -5816,3 +5816,9 @@ Before: agents can read the library; supporting-path and push-credential depende
2026-10-26 | Sage | mos1884/#662 scope after330/331landing | ReadQ222/Q261/Q263 and659/660/662; posted bounded human-only factory settings/triage/defaulttwo-roundmandate/trackedno-exemptionflake scope with10acceptance criteria, task662comment1338 exactwholebody/author13GETverified. No implementation/review/gate launched.660c1335notready(review/merge, noVM); gatefacts1329/1334 retained; target integration/fullrunner confirmation follows readiness, no localfallback. Landedproduct/evidence unchanged;657/658backlog.
2026-10-26 | Sage | Mos1909/1915/1917 real postlanding reviews | Boyd/Dewey/Filbert/Gladstone real authored candidate52ba verdictfiles independently byte/hash-verified, allAPPROVE scoped; Dewey656broaderPARTIAL retained. Formal330R1/331R4 requests failedpre-send/no comments or approvals; no borrowed/copied/mintedkeys. LegacyhelperGETname jason.woltje is NOT ownJarvis, ownslotabsent; rows tracking-open/#436, files cited via normalqueue. NewFilbertNEW-330-01 filedtask665 exactreadback, notlandingreopen/gate. Repair646-648/652-656 remainOPEN untilactualcomments;1917 conditionalbacklog routing retained. #662scope1338posted;660PR405REVISE/notready. Product1968inputs unchanged; no source/helper/test/gate edits/runs; peerjournal prefixes preserved.
2026-10-28T22:34:42-05:00 | Sage FIX-713 seam investigation AFTER/BLOCKED | Branch sage/q284-713-pi-undici at exact base 09a847edaa25b609bac7728ff83f224708a29399. Checksum-verified Pi0.85.1 registry artifact (lock SHA512 FGRN+OHb...L/oQ==) maps bin pi to dist/bundle/cli.js; that CLI imports chunk-JVUZSMYM.js, which embeds node_modules/undici/index.js and Undici8.9.0 advisory signatures (orphaned fetch body before content-encoding reject; HTTP1/2 ERR_TLS_CERT_ALTNAME_INVALID assertions). Exact upstream v0.85.1 bundler bundles all non-allowlisted packages and does not externalize Undici; root8.12.2 override would therefore be installed but not consumed. Public registry metadata latest1.4.0 and all published declared Undici values remain below8.12.2. Read-only focused source verification PASS; no product/lock/test change, no full gate/container/native/provider/live/API operation, commit, push or PR. Minimal missing boundary is an upstream exact pinned Pi artifact rebuilt with bundled Undici>=8.12.2, or separate authorization and compatibility admission for a rebuilt/alternate entry artifact. Owned scratch /mnt/storage/scratch/tmp/q284-713.l8x9AG disposed.
2026-10-29T00:37:47-05:00 | Sage Mos2248/FIX-713 source-only rebuild recipe AFTER/READY-FOR-PARENT-REVIEW | On sage/q284-713-pi-undici at unchanged 09a847edaa25b609bac7728ff83f224708a29399, added only packages/pi-rebuild plus append-only receipts. Exact Pi v0.85.1 commit d981de1 source/tool/package pins; zero-context three-target patch sha256 6f1c3aaf5c6bdf08cfb02932d77251d8048130ba59c63a099b25cae26cb0bf0e moves bundled Undici8.9.0->8.12.2 and full-A5 brace-expansion5.0.9->5.0.12, retaining all unrelated pins and the truthful unchanged public-registry install-lock. Actual published CLI closure=40 files, contains chunk-JVUZSMYM.js, has no external Undici import, and fixed verifier refuses its8.9.0 shrinkwrap. Exact clean/preimage and patched/postimage validation PASS; 3 fixture tests PASS (fail-closed pins/patch, source graph, reachable-bundle signatures plus deterministic manifest/SPDX/provenance logic); bash/node syntax and tracked/untracked whitespace checks PASS. Recipe/build/CI instructions explicitly UNRUN; no fetched-code execution, package install, rebuild/artifact, runtime/package-lock adoption, full gate, container/native/provider/live/credential/API/commit/push/PR/release/publish/spend action. Later consumption still needs Jason choice: immutable internal/local artifact+hash adoption or fork/new identity/version. Owned scratch /mnt/storage/scratch/tmp/q284-713-recipe.U19vvs disposed.
2026-10-29T00:51:13-05:00 | Sage Mos2248/FIX-713 parent-precheck correction AFTER/READY-FOR-PARENT-REVIEW | CORRECTION to the 00:37 entry: its custom-prefix scratch `/mnt/storage/scratch/tmp/q284-713-recipe.U19vvs` did not satisfy the required exact `mktemp -d -p /mnt/storage/scratch/tmp` command (parent confirmed it absent), and its readiness claim preceded discovery of stale Node22.19.0 and incomplete whitespace handling in the TLS signature guard. Corrected the still-uncommitted recipe to exact official patched Node22.23.4/npm10.9.9 (archive sha256 1805bcd35e6815559bd2eb97db438d3fdb0eb3a5e6a977b7c1badca76337e9ce; PGP unverified; binary unobserved), retained Pi0.85.1/esbuild0.28.1/all unrelated pins and unchanged patch6f1c3aaf. Parent TLS control RED2/1 reproduced, then GREEN3/0 with all three compact/spaced/assert-space vulnerable forms refused while the fixed abort and non-vulnerable equality control remain accepted; the guard is explicitly only a known-direct-signature scan, not universal JS proof. Exact source trace selected upstream `build:offline`; regular AI build has live catalog fetches, offline build only validates committed model data/compiles/copies, and `check:package-install` uses ignore-scripts registry materialization plus PI_OFFLINE/telemetry-off import/`--version` smoke. Driver now enforces owned mode0700 exact-shaped mktemp root, empty user/global npm configs, offline build mode and three build-input hosts. Fresh exact archive clean/preimage and patched/postimage validation PASS; published Pi reference sha/size reverified, actual CLI closure40/chunk-JVUZSMYM/no external Undici/embedded8.9 vulnerable signatures reconfirmed and refused; Undici8.12.2 and brace5.0.12 package sha/size plus four fixed-source hashes reverified; recipe fixtures3/0, validate-recipe, bash/node/JSON syntax and tracked/untracked whitespace PASS. No Node binary download/execution, fetched-code execution, install, rebuild/artifact, Mosaic compatibility/full gate, runtime/lock adoption, provider/live/credential/API/commit/push/PR/release/publish/spend action. Compliant owned scratch `/mnt/storage/scratch/tmp/tmp.lqYHVE1uKt` disposed; parent review remains before commit.
+6
View File
@@ -1879,3 +1879,9 @@ are never rewritten or removed; corrections are new entries.
2026-10-26 | Sage | mos1884/#662 scope after330/331landing | ReadQ222/Q261/Q263 and659/660/662; posted bounded human-only factory settings/triage/defaulttwo-roundmandate/trackedno-exemptionflake scope with10acceptance criteria, task662comment1338 exactwholebody/author13GETverified. No implementation/review/gate launched.660c1335notready(review/merge, noVM); gatefacts1329/1334 retained; target integration/fullrunner confirmation follows readiness, no localfallback. Landedproduct/evidence unchanged;657/658backlog.
2026-10-26 | Sage | Mos1909/1915/1917 real postlanding reviews | Boyd/Dewey/Filbert/Gladstone real authored candidate52ba verdictfiles independently byte/hash-verified, allAPPROVE scoped; Dewey656broaderPARTIAL retained. Formal330R1/331R4 requests failedpre-send/no comments or approvals; no borrowed/copied/mintedkeys. LegacyhelperGETname jason.woltje is NOT ownJarvis, ownslotabsent; rows tracking-open/#436, files cited via normalqueue. NewFilbertNEW-330-01 filedtask665 exactreadback, notlandingreopen/gate. Repair646-648/652-656 remainOPEN untilactualcomments;1917 conditionalbacklog routing retained. #662scope1338posted;660PR405REVISE/notready. Product1968inputs unchanged; no source/helper/test/gate edits/runs; peerjournal prefixes preserved.
2026-10-28T22:34:42-05:00 | Sage | FIX-713 Pi Undici consumer seam on sage/q284-713-pi-undici | Exact verified Pi0.85.1 bundle embeds vulnerable Undici8.9.0; no external override seam or compatible in-scope fix. Focused source assertions PASS; required upstream rebuilt exact artifact or separately reviewed alternate-entry/rebuild compatibility boundary reported. No product/lock/test/full-gate/container/native/provider/live/API/commit/push/PR action; owned scratch disposed.
2026-10-29T00:37:47-05:00 | Sage | Mos2248/FIX-713 source-only Pi0.85.1 rebuild recipe on sage/q284-713-pi-undici | Prepared review-ready exact-source recipe, three-file upstream patch (Undici8.12.2 plus A5-required brace-expansion5.0.12), fail-closed pre/postimage and actual bundle-closure/signature checks, deterministic manifest/SPDX/provenance writers, and UNRUN focused CI/no-model acceptance. Local source/fixture/static evidence PASS; actual rebuild/artifact/adoption/CI/full gate and all protected operations unrun. Parent review required before commit; later consumption needs Jason artifact-source identity choice. Owned scratch /mnt/storage/scratch/tmp/q284-713-recipe.U19vvs disposed.
2026-10-29T00:51:13-05:00 | Sage | FIX-713 parent-precheck correction on sage/q284-713-pi-undici at base09a847ed | Supersedes prior readiness defects: records custom-prefix scratch deviation, updates exact unrun toolchain to patched Node22.23.4/npm10.9.9, uses exact upstream build:offline after source-tracing model/network and check:package-install behavior, fixes known direct TLS signature whitespace discrimination with parent RED2/1→GREEN3/0 controls, and narrows driver/README cleanup to exact owned mode0700 mktemp roots. Exact source/patched/bundle/package/static checks PASS; build/install/artifact/Mosaic compatibility/full CI and protected operations remain UNRUN; no commit/push/review dispatch. Compliant scratch /mnt/storage/scratch/tmp/tmp.lqYHVE1uKt disposed; prior custom-prefix path confirmed absent.
+151
View File
@@ -0,0 +1,151 @@
{
"schemaVersion": 1,
"state": "UNRUN",
"ruling": "Mos2248",
"pi": {
"name": "@earendil-works/pi-coding-agent",
"version": "0.85.1",
"tag": "v0.85.1",
"commit": "d981de1229ef899957bbe968bc8dcda02a21f477",
"tree": "346294a615d2d0ad4f6e5fbccb4cee4ccd7b2d6c",
"commitTimestamp": "2026-09-05T11:54:46Z",
"sourceDateEpoch": 1788609286,
"source": {
"url": "https://codeload.github.com/earendil-works/pi/tar.gz/d981de1229ef899957bbe968bc8dcda02a21f477",
"archiveRoot": "pi-d981de1229ef899957bbe968bc8dcda02a21f477",
"bytes": 6978387,
"sha256": "d2a04b35e57f59873d67f667f22e681e93c78a46ba8e2cdedf9d4c323b1ae8dc",
"sha512": "77acb7819e75da17059cb507064739d6eaf033ad76b9a5d2b090cb607bf97ddff5a94ec0ae57ce5f1c168c95f370b6e97513e667fdb47b53ed55f486286a2ae9"
},
"registryReference": {
"metadataUrl": "https://registry.npmjs.org/@earendil-works%2fpi-coding-agent/0.85.1",
"metadataSha256": "3f6f840ec6df9cc731e6c9d1dca4ca31ffa609797fb8b0ee78aaf34e2981c3c0",
"tarballUrl": "https://registry.npmjs.org/@earendil-works/pi-coding-agent/-/pi-coding-agent-0.85.1.tgz",
"tarballBytes": 6986356,
"tarballSha256": "1f498729649bdce647d1160993b4d92bf3c614cc819213bee2f91dd34f2a7af4",
"tarballSha512": "14644df8e1db59a79f04f19a4e080074b8eb2075beb363f32f2825cffe5d7cbcdbf687fbbae5cc5d80b47c921e653c3eb214b7da8d9cb90f6317b6120ee2ffa1",
"integrity": "sha512-FGRN+OHbWaefBPGaTggAdLjrIHW+s2PzLyglz/5dfLzb9of7uuXMXYC0fJIeZTw+shS32o2cuQ9jF7YSDuL/oQ==",
"shasum": "4cd00f653c3dabeb193b46f511044e7fbfe0f947",
"gitHead": "d981de1229ef899957bbe968bc8dcda02a21f477",
"attestationsUrl": "https://registry.npmjs.org/-/npm/v1/attestations/@earendil-works%[email protected]",
"attestationsSha256": "bb337af8436f7596a127d43c067d10103ede1d4aa5ebe143a2dafdc9d24291bd"
}
},
"undici": {
"name": "undici",
"version": "8.12.2",
"gitHead": "bb6cc0ae9b6827ed273ed5ba344148273629638f",
"metadata": {
"url": "https://registry.npmjs.org/undici/8.12.2",
"sha256": "9da5247dcaedb24bde2a1434c418f067f0e19ef272a032e99588076c14ec2487"
},
"tarball": {
"url": "https://registry.npmjs.org/undici/-/undici-8.12.2.tgz",
"bytes": 542130,
"sha256": "a765b5e63ab5c6b1b91a59072c88f7857b36f47b09c376762241d177279cdefd",
"sha512": "5edb8359576a4cbaf7340c66da5cbdfe153a914b093a01a1d51012ccc05bb30e9a58239a4331dd5be4100df7444637c9d95e6f2f9a513dae117ad3bd51f8af0e",
"integrity": "sha512-XtuDWVdqTLr3NAxm2ly9/hU6kUsJOgGh1RASzMBbsw6aWCOaQzHdW+QQDfdERjfJ2V5vL5pRPa4RetO9UfivDg==",
"shasum": "e869237430adcc5de6bca798c94edd7db9dac5ac"
},
"attestations": {
"url": "https://registry.npmjs.org/-/npm/v1/attestations/[email protected]",
"sha256": "6b1d108f4e81e752548a8b1cda5aedb4246c02749bd3b30162bf356e6f5709ca"
},
"fixedSourceFiles": {
"lib/web/fetch/index.js": "e3cc1063bb3bb8353f918d2b1c54b8d6991a71b06c4278959f0c2ef412a433b1",
"lib/dispatcher/client-h1.js": "b4473e4ef9e6a3fb5ca0de69fd705c3797d138e7586e40beb49fa13b58c8a370",
"lib/dispatcher/client-h2.js": "9acfef9064b7639a937d5f279e93bafb831ca2e3c018b4e3864b198f97368fb9",
"package.json": "16723528eaa06df7f3b29ea52584e9127608fd9e8de066c39339aa806ad9da84"
}
},
"braceExpansion": {
"name": "brace-expansion",
"version": "5.0.12",
"gitHead": "f3410159d768f56c9d9f4511d3e1b46425fc1099",
"metadata": {
"url": "https://registry.npmjs.org/brace-expansion/5.0.12",
"sha256": "5421e68ea58a0286041e3f11998da4fa44055026b11c527cc14ec74895b58929"
},
"tarball": {
"url": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz",
"bytes": 12620,
"sha256": "ef8448ec78f20b692f04fa6d01f39b5ab34c66404bea3429f5a39c6c9e0be8b4",
"sha512": "628bd0debce168b308ac38cd0cc90d4b4d6d79af77aa11211b9c72f1febe47497e770dca8bee6c0a822823de368ae2d94c75a881692d830543854d3d88d12299",
"integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==",
"shasum": "995fbb4750a77c4d16a7dc942ff2ca6ef8e675ec"
}
},
"toolchain": {
"platform": "linux-x64",
"node": {
"version": "22.23.4",
"url": "https://nodejs.org/dist/v22.23.4/node-v22.23.4-linux-x64.tar.xz",
"sha256": "1805bcd35e6815559bd2eb97db438d3fdb0eb3a5e6a977b7c1badca76337e9ce",
"releaseUrl": "https://nodejs.org/en/blog/release/v22.23.4",
"securityReleaseUrl": "https://nodejs.org/en/blog/vulnerability/october-2026-security-releases",
"shasumsUrl": "https://nodejs.org/dist/v22.23.4/SHASUMS256.txt",
"shasumsSha256": "c90c3f1478fda8308b22cc1a1b0dc536169196444e67ee8b5c582fd351d02bce",
"pgpSignatureVerified": false
},
"npm": {
"version": "10.9.9",
"packageJsonUrl": "https://raw.githubusercontent.com/nodejs/node/v22.23.4/deps/npm/package.json",
"packageJsonSha256": "7c04bf69824d17a1db64defd3a9a86d538781bf2d6ca2764e3f1143fa5b71166"
},
"esbuild": {
"version": "0.28.1"
}
},
"patch": {
"path": "patches/pi-0.85.1-undici-8.12.2.patch",
"sha256": "6f1c3aaf5c6bdf08cfb02932d77251d8048130ba59c63a099b25cae26cb0bf0e",
"allowedTargets": [
"package-lock.json",
"packages/coding-agent/npm-shrinkwrap.json",
"packages/coding-agent/package.json"
]
},
"sourcePreimages": {
".npmrc": "d2c8ca514b23b7378b7ceb25e9599954e7c9d83328dc53185e2b69ba1b2dc146",
"package.json": "7e305a4e6382517028f3b6fdf2b2350c38fa89a24e2cbec6ac84e8d99f99ba1d",
"package-lock.json": "e3569b99e673a0051be908f2bed57651b90a966a5a0d208913c29e6830a6a6c2",
"packages/ai/package.json": "b54df5a36d523febdeebfc5682dc4faed101fee10aba913d5f3679582f018da3",
"packages/ai/scripts/check-model-data.ts": "818e463211322ab41fe4c82eb8e9543fe6b40c1bfb734999d49d87f2071802aa",
"packages/ai/scripts/generate-models.ts": "188383e7107430d2c3321a8435e3295daf0877317cbab94b67a58e6afaec41d6",
"packages/ai/scripts/model-data.ts": "9265bb6f494765be06ad42e50b6f5667c88b690670e9dfd673cb55513d183056",
"packages/coding-agent/package.json": "f1738e4b42203e5f22bcb513f13fb2fb224f1e98d1f129ff042f87048665a94c",
"packages/coding-agent/npm-shrinkwrap.json": "5ff461d87e0a7ccac07e6a9eb3edcd18841d3373d375bed043606b51082f1a26",
"packages/coding-agent/install-lock/package.json": "9079bdf9f1b920e228bee48a3f5aae8c2b83361e3a518436616b91b0445ead33",
"packages/coding-agent/install-lock/package-lock.json": "48d12884e64315740002a71c9aeed8a07f435163f702f1e3582251607fd11ea3",
"scripts/build-coding-agent-bundle.mjs": "ba226d9c812ee1610cf61a0ad3efa8920f672a309fb32d122b2b3a4148071e8a",
"scripts/generate-coding-agent-shrinkwrap.mjs": "494af1eda0552f570df6e4173e9db58b16414db91de43256c9abe0d3bd69067b",
"scripts/generate-coding-agent-install-lock.mjs": "1ceab3fe345dd1441a098bc8d147e63216f4e0700779ec3ec3d3a9a1917d57fc",
"scripts/coding-agent-consumer.mjs": "fe947e1e9140ea33567b32705106ce3c725bd98fb024ace2ed8eafe795ac1580",
"scripts/package-workspaces.mjs": "4214d03e1fdcbe245b903ff938da50c3ccf93a0aa762157f6d58f80e0d1ae955",
"scripts/release-packages.mjs": "1e8f1e35011df54a790109fe42441523818d41e0f4815fe7011ffb14d8014827"
},
"sourcePostimages": {
".npmrc": "d2c8ca514b23b7378b7ceb25e9599954e7c9d83328dc53185e2b69ba1b2dc146",
"package.json": "7e305a4e6382517028f3b6fdf2b2350c38fa89a24e2cbec6ac84e8d99f99ba1d",
"package-lock.json": "b54fedeedc2c042caef98e7b2e21c19f99553abaebe269d648d287875ce54657",
"packages/ai/package.json": "b54df5a36d523febdeebfc5682dc4faed101fee10aba913d5f3679582f018da3",
"packages/ai/scripts/check-model-data.ts": "818e463211322ab41fe4c82eb8e9543fe6b40c1bfb734999d49d87f2071802aa",
"packages/ai/scripts/generate-models.ts": "188383e7107430d2c3321a8435e3295daf0877317cbab94b67a58e6afaec41d6",
"packages/ai/scripts/model-data.ts": "9265bb6f494765be06ad42e50b6f5667c88b690670e9dfd673cb55513d183056",
"packages/coding-agent/package.json": "d63c0319c402cb3f855f46dc724a63b771e6e40ca872b698273717f482c5760d",
"packages/coding-agent/npm-shrinkwrap.json": "ac5a5b05a700c98f97d573973c54fd5f875b7e0be63f6f3612f07cc029fb5f65",
"packages/coding-agent/install-lock/package.json": "9079bdf9f1b920e228bee48a3f5aae8c2b83361e3a518436616b91b0445ead33",
"packages/coding-agent/install-lock/package-lock.json": "48d12884e64315740002a71c9aeed8a07f435163f702f1e3582251607fd11ea3",
"scripts/build-coding-agent-bundle.mjs": "ba226d9c812ee1610cf61a0ad3efa8920f672a309fb32d122b2b3a4148071e8a",
"scripts/generate-coding-agent-shrinkwrap.mjs": "494af1eda0552f570df6e4173e9db58b16414db91de43256c9abe0d3bd69067b",
"scripts/generate-coding-agent-install-lock.mjs": "1ceab3fe345dd1441a098bc8d147e63216f4e0700779ec3ec3d3a9a1917d57fc",
"scripts/coding-agent-consumer.mjs": "fe947e1e9140ea33567b32705106ce3c725bd98fb024ace2ed8eafe795ac1580",
"scripts/package-workspaces.mjs": "4214d03e1fdcbe245b903ff938da50c3ccf93a0aa762157f6d58f80e0d1ae955",
"scripts/release-packages.mjs": "1e8f1e35011df54a790109fe42441523818d41e0f4815fe7011ffb14d8014827"
},
"securityEvidence": {
"briefSha256": "83b9462ba40e93934fcf3cc96617f59dff3950d4321e3351eb49ab83a03c1359",
"a5ReportSha256": "71270a41a957921b5a83b09650f817fdaef31ef4e762f03a68c8eed8207fb307",
"a5MetadataSha256": "4ae5158491a25d99778fa84cefe65260c0d6aed701b6e710de2c80ba8e39877f"
}
}
+153
View File
@@ -0,0 +1,153 @@
# Pi 0.85.1 / Undici 8.12.2 source rebuild recipe
Status: **UNRUN**. Mos2248 authorizes preparation of this source-only recipe. It does not authorize running fetched code, installing packages, building or adopting an artifact, switching the runtime, publishing, releasing, spending, using credentials, or contacting a model/provider. Nothing in this directory is a claim that Mosaic currently consumes a fixed bundle.
This directory is intentionally outside Mosaic's root dependency graph. It adds no root override and changes neither `package.json` nor `package-lock.json`. A root override would be cosmetic: Pi 0.85.1's `bin.pi` is `dist/bundle/cli.js`, its esbuild graph has `bundle: true`, and Undici is not allowlisted as external. The published bundle therefore consumes its embedded Undici, not Mosaic's root resolution.
## Exact inputs
`PIN.json` is the machine-readable authority. `src/recipe.mjs` independently enforces its security-critical values and refuses drift.
| Input | Exact identity | SHA-256 |
|---|---|---|
| Pi source | tag `v0.85.1`, commit `d981de1229ef899957bbe968bc8dcda02a21f477`, tree `346294a615d2d0ad4f6e5fbccb4cee4ccd7b2d6c` | source archive `d2a04b35e57f59873d67f667f22e681e93c78a46ba8e2cdedf9d4c323b1ae8dc` |
| Published Pi reference (not a build input) | `@earendil-works/[email protected]`, `gitHead` equal to the commit above | tarball `1f498729649bdce647d1160993b4d92bf3c614cc819213bee2f91dd34f2a7af4` |
| Fixed package | `[email protected]`, `gitHead bb6cc0ae9b6827ed273ed5ba344148273629638f` | tarball `a765b5e63ab5c6b1b91a59072c88f7857b36f47b09c376762241d177279cdefd` |
| A5 companion pin | `[email protected]`, `gitHead f3410159d768f56c9d9f4511d3e1b46425fc1099` | tarball `ef8448ec78f20b692f04fa6d01f39b5ab34c66404bea3429f5a39c6c9e0be8b4` |
| Toolchain | Node `22.23.4` Linux x64 with bundled npm `10.9.9`; esbuild remains `0.28.1` from Pi's lock | Node archive `1805bcd35e6815559bd2eb97db438d3fdb0eb3a5e6a977b7c1badca76337e9ce` |
| Patch | `patches/pi-0.85.1-undici-8.12.2.patch` | `6f1c3aaf5c6bdf08cfb02932d77251d8048130ba59c63a099b25cae26cb0bf0e` |
The Pi npm attestation binds the published 0.85.1 reference tarball to the same upstream tag and commit. The exact GitHub commit is unsigned, so the recipe does not claim a signed source commit. The Undici npm attestation binds 8.12.2 to `bb6cc0ae9b6827ed273ed5ba344148273629638f`. The npm attestation endpoint returned 404 for brace-expansion 5.0.12 during preparation; its exact registry metadata (including registry signatures), tarball integrity, Git head, and hashes are pinned without claiming provenance attestation. Exact metadata and attestation response hashes where present, archive SHA-512 values, source/postimage hashes, and the original FIX-713/A5 evidence hashes are in `PIN.json`.
Node 22.23.4 is the 2026-10-21 security release on the same major line permitted by Pi's unchanged upstream `node >=22.19.0` engine. The official release page and October 2026 security notice identify the 22.x fixes; the official `SHASUMS256.txt` digest, archive digest, and exact `deps/npm/package.json` digest establishing bundled npm 10.9.9 are pinned. Preparation did not download or execute the Node archive and did not verify the published PGP signature, so no binary-observation or PGP-verification claim is made.
## Minimal source change
The patch changes exactly three existing Pi files. It replaces seven Undici `8.9.0` literals and the two `[email protected]` lock resolutions:
- `packages/coding-agent/package.json`: direct dependency only;
- `package-lock.json`: the coding-agent workspace edge plus top-level Undici and brace-expansion resolutions only;
- `packages/coding-agent/npm-shrinkwrap.json`: the packed package edge plus those two resolutions only.
It preserves Pi at 0.85.1, every unrelated pin, and Gondolin's nested `[email protected]`. `[email protected]` is the sole companion change because the full A5 report explicitly requires this remediation's resulting tree to meet that floor; Minimatch's existing `^5.0.5` edge is unchanged. It does not alter the esbuild script: that exact script already bundles non-external dependencies and rejects unexpected external imports.
The stored patch has zero context so the repository's whitespace gate does not misread tab-indented JSON context lines as indentation errors. The driver uses `git apply --unidiff-zero`; exact full-file preimage hashes and the three-path allowlist are checked first.
`packages/coding-agent/install-lock/package-lock.json` deliberately stays byte-identical and still describes the public registry's published Pi 0.85.1, including Undici 8.9.0 and brace-expansion 5.0.9. Rewriting that lock while its dependency still points at public `@earendil-works/[email protected]` would be false evidence. Consequently, the broad upstream `npm run check` is not an admitted command: it includes `check:install-lock:coding-agent`. The recipe instead runs the truthful static/build checks individually and the upstream local-package consumer check.
## Offline build and consumer source trace
The exact checksum-verified Pi source archive, not fetched-code execution, establishes the future command boundary:
- root `package.json` defines `build:offline` with the same workspace build sequence as `build`, except the AI workspace receives `npm run build:offline`;
- `packages/ai/package.json` defines normal `build` as `generate-models` followed by `build:offline`. `generate-models.ts` contains live catalog fetches to models.dev, NVIDIA, OpenRouter, and Vercel AI Gateway, so the driver does not use it;
- AI `build:offline` runs `check:model-data`, TypeScript compilation, and local file copies. `check-model-data.ts` and `model-data.ts` only read, parse, hash, and validate the committed generated catalog; they contain no provider/model call;
- every other workspace build in the root sequence is TypeScript compilation or a local copy/bundle operation. The unchanged esbuild script reads compiled local inputs and embeds non-allowlisted dependencies.
The driver therefore uses upstream's supported `npm run build:offline`, sets npm's offline mode for that build command, and exports `PI_OFFLINE=1` and `PI_TELEMETRY=0`; it does not invent a build hook. The exact upstream `check:package-install` source runs local `npm pack --ignore-scripts`, creates a consumer whose public workspaces are local tarball overrides, and runs `npm install --ignore-scripts --omit=dev --no-audit --no-fund`. Its smoke subprocess imports three SDK symbols and runs the CLI and unbundled CLI with `--version` under an isolated HOME, `PI_OFFLINE=1`, and `PI_TELEMETRY=0`; it neither configures a model nor sends a provider request. The exact root lock and coding-agent shrinkwrap resolve remote packages only from `registry.npmjs.org`. Thus consumer installation remains a future networked CI step limited to the registry host, while its compatibility smoke is explicitly no-model.
The future driver fetches build inputs only from `codeload.github.com`, `registry.npmjs.org`, and `nodejs.org`. `PIN.json` also records a read-only `raw.githubusercontent.com` npm metadata evidence URL, but the driver never fetches it; a three-host CI egress policy must continue to refuse that fourth host.
## Evidence emitted by a future admitted run
The driver validates the unpatched archive against exact preimage hashes, applies the three-file patch, and validates exact postimage hashes before package materialization. After a future build it validates the installed Undici package and four security-relevant source files against the 8.12.2 tarball hashes.
It then opens the packed package and follows relative static/dynamic imports from the actual `package.json` `bin.pi` entry. The reachable closure must:
- resolve `[email protected]` and `[email protected]` with exact npm integrities in the packed shrinkwrap;
- have no external `undici` import;
- contain the fixed `controller.abort(new Error(\`too many content-encodings...` signature;
- omit the vulnerable `reject(new Error(\`too many content-encodings...` signature;
- omit the known direct vulnerable assertion signatures equivalent to `assert(err.code !== "ERR_TLS_CERT_ALTNAME_INVALID")`, including source and minified whitespace forms.
Those are the two A5-002 advisory seams: GHSA-xjj6-f33q-929c (orphaned response stream after excessive content codings) and GHSA-qrxm-fqhp-jwvm (TLS alternate-name assertion termination). These conservative source signatures discriminate the fixed abort from the known vulnerable direct assertions; they are not a parser and do not prove the absence of every semantically equivalent JavaScript program. The recipe makes no broader security claim about Undici or Pi.
On success, `src/receipts.mjs` writes deterministic, tab-formatted receipts:
- `artifact-manifest.json`: artifact hashes, exact material/toolchain pins, actual bundle assertion results, and a sorted hash/mode inventory of every packed file;
- `sbom.spdx.json`: SPDX 2.3 packages and resolved dependency relationships derived from the packed shrinkwrap;
- `provenance.intoto.json`: an in-toto statement with SLSA provenance v1 predicate, source/package/tool/patch materials, and artifact/manifest/SBOM digests;
- `SHA256SUMS`: sorted hashes of the artifact and all three receipts.
Dynamic wall-clock time, scratch paths, hostnames, and CI run IDs are excluded. `SOURCE_DATE_EPOCH` is the pinned upstream commit epoch. CI acceptance still requires two clean builds to prove the upstream build output itself is reproducible.
## Executable instructions — UNRUN
These commands are review material. Do not run them under the current authorization.
```bash
# UNRUN — package preparation checks only; no fetched code or package installation.
cleanup_owned_scratch() {
case "$1" in
/mnt/storage/scratch/tmp/tmp.??????????)
[ -d "$1" ] && [ ! -L "$1" ] || return 2
[ "$(stat -c %u -- "$1")" = "$(id -u)" ] || return 2
[ "$(stat -c %a -- "$1")" = "700" ] || return 2
find "$1" -depth -delete
;;
*) return 2 ;;
esac
}
recipe_scratch="$(mktemp -d -p /mnt/storage/scratch/tmp)"
MOSAIC_PI_REBUILD_TEST_TMP="$recipe_scratch" \
node --test packages/pi-rebuild/tests/recipe.test.mjs
node packages/pi-rebuild/src/cli.mjs validate-recipe
cleanup_owned_scratch "$recipe_scratch"
```
The full driver downloads only the exact pinned Pi source archive, Undici package, brace-expansion package, and Node toolchain; verifies them before use; clears the environment for every fetched-code command; uses an empty HOME, empty user/global npm configs, and owned cache; runs `npm ci --ignore-scripts`; and never publishes or changes Mosaic's dependency graph.
```bash
# UNRUN — requires a separate parent-approved build/CI action.
cleanup_owned_scratch() {
case "$1" in
/mnt/storage/scratch/tmp/tmp.??????????)
[ -d "$1" ] && [ ! -L "$1" ] || return 2
[ "$(stat -c %u -- "$1")" = "$(id -u)" ] || return 2
[ "$(stat -c %a -- "$1")" = "700" ] || return 2
find "$1" -depth -delete
;;
*) return 2 ;;
esac
}
build_scratch="$(mktemp -d -p /mnt/storage/scratch/tmp)"
MOSAIC_PI_REBUILD_EXECUTE=MOS2248_REVIEWED_UNRUN_RECIPE \
packages/pi-rebuild/rebuild.sh "$build_scratch"
# Review/copy results before disposing only this owned directory.
find "$build_scratch/result" -maxdepth 1 -type f -print | sort
cleanup_owned_scratch "$build_scratch"
```
The driver additionally requires that `WORK_ROOT` resolve to an empty, non-symlink, mode-0700 directory owned by the invoking uid, directly under `/mnt/storage/scratch/tmp`, with the exact `tmp.` plus ten-character basename shape emitted by the required `mktemp` command. The guard value is only a fail-closed acknowledgement that the recipe was admitted; it is not release, adoption, publish, credential, provider, or spend authority.
## Admitted CI acceptance — UNRUN
A future job must have `permissions: contents: read`, no secrets, no npm token, no provider credentials, and outbound access restricted to the three build-input HTTPS hosts named above. It must not use `npm publish`, `npm install -g`, a container build, Mosaic release/activation commands, or any model/provider test.
1. Run the three local recipe fixture tests and `validate-recipe`. This proves recipe/input validation, TLS signature discrimination, and deterministic receipt generation against synthetic fixtures only; it does not prove a rebuilt bundle fixed.
2. In two separately created `mktemp -d -p /mnt/storage/scratch/tmp` directories, run `rebuild.sh` with the admitted guard. Compare every same-named file in both `result/` directories byte-for-byte and compare both `SHA256SUMS` files. Any difference refuses.
3. In each result, run `sha256sum --check --strict SHA256SUMS`, unpack the package, and rerun `verify-bundle` against that package and the installed build-tree `node_modules/undici`. This is the actual consumed-bundle/fixed-signature gate.
4. Require the packed CLI and unbundled CLI to print exactly `0.85.1` under an empty HOME with `PI_OFFLINE=1` and `PI_TELEMETRY=0`, matching upstream `check:package-install`. No request is sent and no provider/model is configured.
5. In the Mosaic checkout, run only the no-model seams below. The Pi harness test executes a stand-in binary and fixes the adapter argv contract; the runner test uses its fake adapter/broker and fixes dispatch/publication behavior. Neither consumes credentials or contacts a provider.
```bash
# UNRUN — focused Mosaic compatibility, not the full gate.
node --test packages/plugins/tests/harness.test.mjs
node --test packages/harness/tests/runner.test.mjs
```
6. Review the artifact manifest, SPDX dependency edges for `[email protected]` and `[email protected]`, provenance materials, and absence of changes outside this recipe directory plus append-only logs. Parent then records the CI URLs/hashes; this recipe does not do API, PR, CI, or release operations.
## Remaining boundary
No rebuilt artifact exists yet. Even after admitted CI succeeds, the result has the public npm identity and version `@earendil-works/[email protected]` but different bytes. It cannot be published over that immutable npm version, and this task does not authorize Mosaic to adopt a local tarball or another package source.
Jason must later choose one separately reviewed consumption boundary:
1. approve an immutable internal/local artifact URL plus hash and the corresponding Mosaic `package.json`/lock adoption, while retaining the package's internal 0.85.1 identity; or
2. approve a fork/new package identity or version and its source/publish/release path.
Either choice requires separate adoption, CI, release, and runtime-switch authority. No such choice is needed to review this source-ready recipe.
The generated provenance and SBOM are deterministic but unsigned. A later trusted CI admission must bind their hashes to its authenticated run; this recipe does not claim a trusted-builder signature.
+13
View File
@@ -0,0 +1,13 @@
{
"name": "@mosaic/pi-rebuild-recipe",
"private": true,
"type": "module",
"description": "UNRUN source-only Pi 0.85.1 / Undici 8.12.2 rebuild recipe and evidence validators",
"engines": {
"node": "22.23.4"
},
"scripts": {
"test": "node --test tests/*.test.mjs",
"validate": "node src/cli.mjs validate-recipe"
}
}
@@ -0,0 +1,46 @@
diff --git a/package-lock.json b/package-lock.json
--- a/package-lock.json
+++ b/package-lock.json
@@ -2523,3 +2523,3 @@
- "version": "5.0.9",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
- "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
+ "version": "5.0.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz",
+ "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==",
@@ -4979,3 +4979,3 @@
- "version": "8.9.0",
- "resolved": "https://registry.npmjs.org/undici/-/undici-8.9.0.tgz",
- "integrity": "sha512-aWZpUj7XoGonMClx4gdDRfgBjqeA+F473aDmROQQbM9n6PRfK/u1q/a0X4wMTgcHfT8H6fpbt98PFuDUwFg2YA==",
+ "version": "8.12.2",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-8.12.2.tgz",
+ "integrity": "sha512-XtuDWVdqTLr3NAxm2ly9/hU6kUsJOgGh1RASzMBbsw6aWCOaQzHdW+QQDfdERjfJ2V5vL5pRPa4RetO9UfivDg==",
@@ -5441 +5441 @@
- "undici": "8.9.0",
+ "undici": "8.12.2",
diff --git a/packages/coding-agent/npm-shrinkwrap.json b/packages/coding-agent/npm-shrinkwrap.json
--- a/packages/coding-agent/npm-shrinkwrap.json
+++ b/packages/coding-agent/npm-shrinkwrap.json
@@ -29 +29 @@
- "undici": "8.9.0",
+ "undici": "8.12.2",
@@ -1432,3 +1432,3 @@
- "version": "5.0.9",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
- "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
+ "version": "5.0.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz",
+ "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==",
@@ -2144,3 +2144,3 @@
- "version": "8.9.0",
- "resolved": "https://registry.npmjs.org/undici/-/undici-8.9.0.tgz",
- "integrity": "sha512-aWZpUj7XoGonMClx4gdDRfgBjqeA+F473aDmROQQbM9n6PRfK/u1q/a0X4wMTgcHfT8H6fpbt98PFuDUwFg2YA==",
+ "version": "8.12.2",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-8.12.2.tgz",
+ "integrity": "sha512-XtuDWVdqTLr3NAxm2ly9/hU6kUsJOgGh1RASzMBbsw6aWCOaQzHdW+QQDfdERjfJ2V5vL5pRPa4RetO9UfivDg==",
diff --git a/packages/coding-agent/package.json b/packages/coding-agent/package.json
--- a/packages/coding-agent/package.json
+++ b/packages/coding-agent/package.json
@@ -69 +69 @@
- "undici": "8.9.0",
+ "undici": "8.12.2",
+162
View File
@@ -0,0 +1,162 @@
#!/usr/bin/env bash
# UNRUN: this source-only driver is prepared for a separately authorized CI run.
# It intentionally refuses unless the caller supplies the Mos2248 review token.
set -euo pipefail
fail() { echo "pi rebuild refused: $*" >&2; exit 2; }
[ "$#" -eq 1 ] || fail "usage: rebuild.sh WORK_ROOT"
[ "${MOSAIC_PI_REBUILD_EXECUTE:-}" = "MOS2248_REVIEWED_UNRUN_RECIPE" ] || fail "set MOSAIC_PI_REBUILD_EXECUTE=MOS2248_REVIEWED_UNRUN_RECIPE only in the admitted rebuild job"
script_dir="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P)"
work_root="$1"
[ -d "$work_root" ] && [ ! -L "$work_root" ] || fail "WORK_ROOT must be the non-symlink directory returned by mktemp"
work_root="$(realpath -- "$work_root")"
[ "$(dirname -- "$work_root")" = "/mnt/storage/scratch/tmp" ] || fail "WORK_ROOT must be created by mktemp -d -p /mnt/storage/scratch/tmp"
[[ "$(basename -- "$work_root")" =~ ^tmp\.[A-Za-z0-9]{10}$ ]] || fail "WORK_ROOT name must match the exact mktemp -d -p /mnt/storage/scratch/tmp result"
[ "$(/usr/bin/stat -c %u -- "$work_root")" = "$(/usr/bin/id -u)" ] || fail "WORK_ROOT must be owned by the invoking user"
[ "$(/usr/bin/stat -c %a -- "$work_root")" = "700" ] || fail "WORK_ROOT permissions must be 0700"
[ -z "$(find "$work_root" -mindepth 1 -maxdepth 1 -print -quit)" ] || fail "WORK_ROOT must start empty"
pin_file="$script_dir/PIN.json"
patch_file="$script_dir/patches/pi-0.85.1-undici-8.12.2.patch"
system_path="/usr/local/bin:/usr/bin:/bin"
pin() {
/usr/bin/env -i PATH="$system_path" /usr/bin/node -e '
const value = process.argv[2].split(".").reduce((v, key) => v?.[key], JSON.parse(require("node:fs").readFileSync(process.argv[1], "utf8")));
if (value === undefined || (typeof value === "object" && value !== null)) process.exit(2);
process.stdout.write(String(value));
' "$pin_file" "$1"
}
mkdir -m 0700 -- "$work_root/inputs" "$work_root/home" "$work_root/npm-cache" "$work_root/source-parent" "$work_root/pack" "$work_root/package-root" "$work_root/tmp"
install -m 0600 /dev/null "$work_root/npmrc"
install -m 0600 /dev/null "$work_root/npm-globalrc"
pi_archive="$work_root/inputs/pi-source.tar.gz"
undici_archive="$work_root/inputs/undici-8.12.2.tgz"
brace_archive="$work_root/inputs/brace-expansion-5.0.12.tgz"
node_archive="$work_root/inputs/node-v22.23.4-linux-x64.tar.xz"
fetch() {
url="$1"
output="$2"
/usr/bin/env -i PATH="$system_path" HOME="$work_root/home" \
curl --proto '=https' --tlsv1.2 --location --fail --silent --show-error --output "$output" "$url"
}
fetch "$(pin pi.source.url)" "$pi_archive"
fetch "$(pin undici.tarball.url)" "$undici_archive"
fetch "$(pin braceExpansion.tarball.url)" "$brace_archive"
fetch "$(pin toolchain.node.url)" "$node_archive"
check_file() {
path="$1"
expected_bytes="$2"
expected_sha256="$3"
[ "$(stat -c %s -- "$path")" = "$expected_bytes" ] || fail "byte length mismatch: $path"
printf '%s %s\n' "$expected_sha256" "$path" | sha256sum --check --strict --status || fail "sha256 mismatch: $path"
}
check_file "$pi_archive" "$(pin pi.source.bytes)" "$(pin pi.source.sha256)"
check_file "$undici_archive" "$(pin undici.tarball.bytes)" "$(pin undici.tarball.sha256)"
check_file "$brace_archive" "$(pin braceExpansion.tarball.bytes)" "$(pin braceExpansion.tarball.sha256)"
printf '%s %s\n' "$(pin pi.source.sha512)" "$pi_archive" | sha512sum --check --strict --status || fail "Pi source sha512 mismatch"
printf '%s %s\n' "$(pin undici.tarball.sha512)" "$undici_archive" | sha512sum --check --strict --status || fail "Undici sha512 mismatch"
printf '%s %s\n' "$(pin braceExpansion.tarball.sha512)" "$brace_archive" | sha512sum --check --strict --status || fail "brace-expansion sha512 mismatch"
printf '%s %s\n' "$(pin toolchain.node.sha256)" "$node_archive" | sha256sum --check --strict --status || fail "Node archive sha256 mismatch"
archive_root="$(pin pi.source.archiveRoot)"
tar -tzf "$pi_archive" > "$work_root/pi-members"
while IFS= read -r member; do
case "$member" in
"$archive_root"|"$archive_root"/*) ;;
*) fail "Pi source archive path escapes its pinned root: $member" ;;
esac
case "/$member/" in *"/../"*) fail "Pi source archive contains a parent path: $member" ;; esac
done < "$work_root/pi-members"
tar -tvzf "$pi_archive" | awk '$1 !~ /^[-d]/ { exit 1 }' || fail "Pi source archive contains a link or special entry"
tar -xzf "$pi_archive" -C "$work_root/source-parent"
source_root="$work_root/source-parent/$archive_root"
tar -xJf "$node_archive" -C "$work_root"
node_root="$work_root/node-v22.23.4-linux-x64"
node_bin="$node_root/bin/node"
npm_bin="$node_root/bin/npm"
build_path="$node_root/bin:/usr/local/bin:/usr/bin:/bin"
node_env=(
/usr/bin/env -i
PATH="$build_path"
HOME="$work_root/home"
XDG_CACHE_HOME="$work_root/home/cache"
TMPDIR="$work_root/tmp"
LANG=C.UTF-8
LC_ALL=C.UTF-8
TZ=UTC
CI=1
SOURCE_DATE_EPOCH="$(pin pi.sourceDateEpoch)"
npm_config_cache="$work_root/npm-cache"
npm_config_userconfig="$work_root/npmrc"
npm_config_globalconfig="$work_root/npm-globalrc"
npm_config_registry=https://registry.npmjs.org
npm_config_audit=false
npm_config_fund=false
npm_config_update_notifier=false
PI_OFFLINE=1
PI_TELEMETRY=0
)
# Even the first fetched-tool version probes use the closed build environment.
[ "$("${node_env[@]}" "$node_bin" --version)" = "v$(pin toolchain.node.version)" ] || fail "Node executable version mismatch"
[ "$("${node_env[@]}" "$npm_bin" --version)" = "$(pin toolchain.npm.version)" ] || fail "npm executable version mismatch"
"${node_env[@]}" "$node_bin" "$script_dir/src/cli.mjs" validate-recipe --source-root "$source_root"
git -C "$source_root" apply --unidiff-zero --check "$patch_file"
git -C "$source_root" apply --unidiff-zero "$patch_file"
"${node_env[@]}" "$node_bin" "$script_dir/src/cli.mjs" validate-patched --source-root "$source_root"
# Network materialization is lock/integrity constrained; package lifecycle scripts stay disabled.
(cd "$source_root" && "${node_env[@]}" "$npm_bin" ci --ignore-scripts --no-audit --no-fund)
# These are the source/build checks that remain truthful with the registry install-lock intentionally unchanged.
(cd "$source_root" && "${node_env[@]}" npm_config_offline=true "$npm_bin" run build:offline)
(cd "$source_root" && "${node_env[@]}" "$npm_bin" run check:pinned-deps)
(cd "$source_root" && "${node_env[@]}" "$npm_bin" run check:runtime-deps)
(cd "$source_root" && "${node_env[@]}" "$npm_bin" run check:ts-imports)
(cd "$source_root" && "${node_env[@]}" "$npm_bin" run check:entry-graphs)
(cd "$source_root" && "${node_env[@]}" "$npm_bin" run check:shrinkwrap)
(cd "$source_root" && "${node_env[@]}" "$source_root/node_modules/.bin/tsgo" --noEmit)
(cd "$source_root" && "${node_env[@]}" "$npm_bin" run check:browser-smoke)
(cd "$source_root" && "${node_env[@]}" "$npm_bin" run check:package-install)
pack_json="$(cd "$source_root/packages/coding-agent" && "${node_env[@]}" "$npm_bin" pack --ignore-scripts --json --pack-destination "$work_root/pack")"
artifact_name="$("${node_env[@]}" "$node_bin" -e '
const parsed = JSON.parse(process.argv[1]);
const entry = Array.isArray(parsed) ? parsed[0] : Object.values(parsed)[0];
if (!entry?.filename || /[\\/]/.test(entry.filename)) process.exit(2);
process.stdout.write(entry.filename);
' "$pack_json")" || fail "npm pack did not return one safe filename"
artifact="$work_root/pack/$artifact_name"
[ -f "$artifact" ] && [ ! -L "$artifact" ] || fail "packed artifact is absent or unsafe"
tar -tzf "$artifact" | awk '$0 !~ /^package\// || /(^|\/)\.\.($|\/)/ { exit 1 }' || fail "packed artifact contains an unsafe member"
tar -xzf "$artifact" -C "$work_root/package-root"
package_root="$work_root/package-root/package"
"${node_env[@]}" "$node_bin" "$script_dir/src/cli.mjs" verify-bundle \
--package-root "$package_root" \
--installed-undici-root "$source_root/node_modules/undici"
mkdir -m 0700 "$work_root/result"
"${node_env[@]}" MOSAIC_PI_REBUILD_NPM_VERSION="$(pin toolchain.npm.version)" \
"$node_bin" "$script_dir/src/cli.mjs" write-receipts \
--artifact "$artifact" \
--package-root "$package_root" \
--installed-undici-root "$source_root/node_modules/undici" \
--output "$work_root/result"
install -m 0600 "$artifact" "$work_root/result/$artifact_name"
(cd "$work_root/result" && sha256sum --check --strict SHA256SUMS)
printf 'UNPUBLISHED source rebuild prepared at %s\n' "$work_root/result"
+91
View File
@@ -0,0 +1,91 @@
#!/usr/bin/env node
import { readFileSync } from "node:fs";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import {
assertBundleConsumption,
validatePatch,
validatePatchedTree,
validatePin,
validateSourceTree,
} from "./recipe.mjs";
import { writeReceipts } from "./receipts.mjs";
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const pin = JSON.parse(readFileSync(join(packageRoot, "PIN.json"), "utf8"));
const patch = readFileSync(join(packageRoot, pin.patch.path), "utf8");
function usage(message) {
if (message) process.stderr.write(`${message}\n`);
process.stderr.write("usage: cli.mjs validate-recipe [--source-root DIR] | validate-patched --source-root DIR | verify-bundle --package-root DIR --installed-undici-root DIR | write-receipts --artifact FILE --package-root DIR --installed-undici-root DIR --output DIR\n");
process.exit(2);
}
function options(args, allowed) {
const parsed = {};
for (let index = 0; index < args.length; index += 2) {
const name = args[index];
const value = args[index + 1];
if (!name?.startsWith("--") || value === undefined || value.startsWith("--")) usage(`invalid option sequence at ${name ?? "<end>"}`);
const key = name.slice(2);
if (!allowed.has(key) || parsed[key] !== undefined) usage(`unknown or repeated option: ${name}`);
parsed[key] = resolve(value);
}
return parsed;
}
function requireOptions(parsed, names) {
for (const name of names) if (!parsed[name]) usage(`--${name} is required`);
}
function print(value) {
process.stdout.write(`${JSON.stringify(value, null, "\t")}\n`);
}
const [command, ...args] = process.argv.slice(2);
try {
validatePin(pin);
const patchEvidence = validatePatch(patch, pin);
switch (command) {
case "validate-recipe": {
const parsed = options(args, new Set(["source-root"]));
const source = parsed["source-root"] ? validateSourceTree(parsed["source-root"], pin) : undefined;
print({ state: pin.state, pin: "valid", patch: patchEvidence, ...(source ? { source } : {}) });
break;
}
case "validate-patched": {
const parsed = options(args, new Set(["source-root"]));
requireOptions(parsed, ["source-root"]);
print({ state: pin.state, patchedSource: validatePatchedTree(parsed["source-root"], pin) });
break;
}
case "verify-bundle": {
const parsed = options(args, new Set(["package-root", "installed-undici-root"]));
requireOptions(parsed, ["package-root", "installed-undici-root"]);
print({ state: pin.state, bundle: assertBundleConsumption(parsed["package-root"], pin, { installedUndiciRoot: parsed["installed-undici-root"] }) });
break;
}
case "write-receipts": {
const parsed = options(args, new Set(["artifact", "package-root", "installed-undici-root", "output"]));
requireOptions(parsed, ["artifact", "package-root", "installed-undici-root", "output"]);
const npmVersion = process.env.MOSAIC_PI_REBUILD_NPM_VERSION;
if (!npmVersion) throw new Error("MOSAIC_PI_REBUILD_NPM_VERSION is required");
print({ state: pin.state, receipts: writeReceipts({
artifactPath: parsed.artifact,
packageRoot: parsed["package-root"],
installedUndiciRoot: parsed["installed-undici-root"],
outputDirectory: parsed.output,
pin,
toolVersions: { node: process.version, npm: npmVersion },
}) });
break;
}
default:
usage(command ? `unknown command: ${command}` : "command is required");
}
} catch (error) {
process.stderr.write(`${error?.stack ?? error}\n`);
process.exit(1);
}
+242
View File
@@ -0,0 +1,242 @@
import { createHash } from "node:crypto";
import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
import { basename, join, posix, relative, resolve, sep } from "node:path";
import { assertBundleConsumption, constants } from "./recipe.mjs";
function refuse(condition, message) {
if (!condition) throw new Error(`pi rebuild receipts refused: ${message}`);
}
function hashBuffer(contents, algorithm = "sha256") {
return createHash(algorithm).update(contents).digest("hex");
}
function fileRecord(path) {
const stat = lstatSync(path);
refuse(stat.isFile() && !stat.isSymbolicLink(), `not a regular non-symlink file: ${path}`);
const contents = readFileSync(path);
return { bytes: contents.length, sha256: hashBuffer(contents), sha512: hashBuffer(contents, "sha512") };
}
function jsonFile(path) {
return JSON.parse(readFileSync(path, "utf8"));
}
function writeJson(path, value) {
writeFileSync(path, `${JSON.stringify(value, null, "\t")}\n`, { encoding: "utf8", flag: "wx", mode: 0o600 });
}
function walk(root, directory = root, records = []) {
for (const entry of readdirSync(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
const path = join(directory, entry.name);
refuse(!entry.isSymbolicLink(), `package inventory refuses symlink: ${path}`);
if (entry.isDirectory()) walk(root, path, records);
else {
refuse(entry.isFile(), `package inventory refuses non-file: ${path}`);
const record = fileRecord(path);
record.path = relative(root, path).split(sep).join("/");
record.mode = (lstatSync(path).mode & 0o777).toString(8).padStart(4, "0");
records.push(record);
}
}
return records.sort((a, b) => a.path.localeCompare(b.path));
}
function packageName(lockPath, entry, rootManifest) {
if (lockPath === "") return rootManifest.name;
if (entry.name) return entry.name;
const marker = "node_modules/";
const index = lockPath.lastIndexOf(marker);
refuse(index >= 0, `cannot derive package name from shrinkwrap path ${lockPath}`);
const parts = lockPath.slice(index + marker.length).split("/");
return parts[0].startsWith("@") ? `${parts[0]}/${parts[1]}` : parts[0];
}
function spdxId(lockPath) {
return `SPDXRef-Package-${hashBuffer(lockPath || "root").slice(0, 24)}`;
}
function integrityChecksum(integrity) {
if (typeof integrity !== "string") return undefined;
const match = integrity.match(/^sha512-(.+)$/);
if (!match) return undefined;
return { algorithm: "SHA512", checksumValue: Buffer.from(match[1], "base64").toString("hex") };
}
function dependencyNames(entry) {
return Object.keys({ ...(entry.dependencies ?? {}), ...(entry.optionalDependencies ?? {}) }).sort();
}
function resolveDependency(packages, name, from) {
let current = from;
const candidates = [];
while (true) {
candidates.push(current ? `${current}/node_modules/${name}` : `node_modules/${name}`);
if (!current) break;
const parent = posix.dirname(current);
current = parent === "." || parent === current ? "" : parent;
}
return candidates.find((candidate) => packages[candidate] && !packages[candidate].link);
}
function createSpdx(packageRoot, artifact, pin) {
const manifest = jsonFile(join(packageRoot, "package.json"));
const lock = jsonFile(join(packageRoot, "npm-shrinkwrap.json"));
const entries = Object.entries(lock.packages ?? {})
.filter(([, entry]) => !entry.link)
.sort(([a], [b]) => a.localeCompare(b));
const packages = entries.map(([lockPath, entry]) => {
const name = packageName(lockPath, entry, manifest);
const version = entry.version ?? manifest.version;
const checksum = integrityChecksum(entry.integrity);
return {
SPDXID: spdxId(lockPath),
name,
versionInfo: version,
downloadLocation: entry.resolved ?? "NOASSERTION",
filesAnalyzed: false,
licenseConcluded: "NOASSERTION",
licenseDeclared: entry.license ?? "NOASSERTION",
copyrightText: "NOASSERTION",
...(checksum ? { checksums: [checksum] } : {}),
externalRefs: [{
referenceCategory: "PACKAGE-MANAGER",
referenceType: "purl",
referenceLocator: `pkg:npm/${encodeURIComponent(name).replaceAll("%2F", "/")}@${encodeURIComponent(version)}`,
}],
comment: `npm shrinkwrap path: ${lockPath || "<root>"}`,
};
});
const relationships = [{
spdxElementId: "SPDXRef-DOCUMENT",
relationshipType: "DESCRIBES",
relatedSpdxElement: spdxId(""),
}];
for (const [lockPath, entry] of entries) {
for (const name of dependencyNames(entry)) {
const target = resolveDependency(lock.packages, name, lockPath);
refuse(target, `cannot resolve ${name} from shrinkwrap path ${lockPath || "<root>"}`);
relationships.push({
spdxElementId: spdxId(lockPath),
relationshipType: "DEPENDS_ON",
relatedSpdxElement: spdxId(target),
});
}
}
relationships.sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)));
return {
spdxVersion: "SPDX-2.3",
dataLicense: "CC0-1.0",
SPDXID: "SPDXRef-DOCUMENT",
name: `${manifest.name}@${manifest.version}-undici-${pin.undici.version}`,
documentNamespace: `https://mosaicstack.invalid/spdx/pi-rebuild/${artifact.sha256}`,
creationInfo: {
created: pin.pi.commitTimestamp,
creators: ["Tool: @mosaic/pi-rebuild-recipe"],
},
documentDescribes: [spdxId("")],
packages,
relationships,
};
}
function createManifest(packageRoot, artifactPath, pin, evidence, toolVersions) {
const artifact = fileRecord(artifactPath);
return {
schemaVersion: 1,
identity: {
name: constants.piName,
version: constants.piVersion,
undici: constants.undiciVersion,
braceExpansion: constants.braceExpansionVersion,
},
artifact: { file: basename(artifactPath), ...artifact },
inputs: {
piSource: { url: pin.pi.source.url, sha256: pin.pi.source.sha256, sha512: pin.pi.source.sha512 },
undiciTarball: { url: pin.undici.tarball.url, sha256: pin.undici.tarball.sha256, sha512: pin.undici.tarball.sha512 },
braceExpansionTarball: { url: pin.braceExpansion.tarball.url, sha256: pin.braceExpansion.tarball.sha256, sha512: pin.braceExpansion.tarball.sha512 },
patch: { path: pin.patch.path, sha256: pin.patch.sha256 },
node: { url: pin.toolchain.node.url, sha256: pin.toolchain.node.sha256 },
},
toolchain: {
node: toolVersions.node,
npm: toolVersions.npm,
esbuild: pin.toolchain.esbuild.version,
platform: pin.toolchain.platform,
},
consumedBundle: evidence,
packageFiles: walk(packageRoot),
};
}
function createProvenance(artifactPath, artifact, manifestHash, sbomHash, pin, toolVersions) {
return {
_type: "https://in-toto.io/Statement/v1",
subject: [{
name: basename(artifactPath),
digest: { sha256: artifact.sha256, sha512: artifact.sha512 },
}],
predicateType: "https://slsa.dev/provenance/v1",
predicate: {
buildDefinition: {
buildType: "https://mosaicstack.invalid/buildtypes/pi-source-rebuild/v1",
externalParameters: {
pi: { name: constants.piName, version: constants.piVersion, commit: pin.pi.commit },
undici: { version: constants.undiciVersion },
braceExpansion: { version: constants.braceExpansionVersion },
sourceDateEpoch: pin.pi.sourceDateEpoch,
},
internalParameters: {
patchSha256: pin.patch.sha256,
manifestSha256: manifestHash,
sbomSha256: sbomHash,
toolchain: toolVersions,
},
resolvedDependencies: [
{ uri: pin.pi.source.url, digest: { sha256: pin.pi.source.sha256, sha512: pin.pi.source.sha512 } },
{ uri: pin.undici.tarball.url, digest: { sha256: pin.undici.tarball.sha256, sha512: pin.undici.tarball.sha512 } },
{ uri: pin.braceExpansion.tarball.url, digest: { sha256: pin.braceExpansion.tarball.sha256, sha512: pin.braceExpansion.tarball.sha512 } },
{ uri: pin.toolchain.node.url, digest: { sha256: pin.toolchain.node.sha256 } },
{ uri: `file:${pin.patch.path}`, digest: { sha256: pin.patch.sha256 } },
{ uri: "file:package-lock.json", digest: { sha256: pin.sourcePostimages["package-lock.json"] } },
],
},
runDetails: {
builder: { id: "https://mosaicstack.invalid/builders/pi-source-rebuild/v1" },
metadata: { reproducibleEpoch: pin.pi.sourceDateEpoch },
},
},
};
}
export function writeReceipts({ artifactPath, packageRoot, installedUndiciRoot, outputDirectory, pin, toolVersions }) {
refuse(toolVersions?.node === `v${pin.toolchain.node.version}`, `Node must be v${pin.toolchain.node.version}`);
refuse(toolVersions?.npm === pin.toolchain.npm.version, `npm must be ${pin.toolchain.npm.version}`);
const artifact = fileRecord(artifactPath);
const evidence = assertBundleConsumption(packageRoot, pin, { installedUndiciRoot });
const output = resolve(outputDirectory);
if (existsSync(output)) refuse(readdirSync(output).length === 0, `receipt directory is not empty: ${output}`);
else mkdirSync(output, { recursive: false, mode: 0o700 });
const manifest = createManifest(packageRoot, artifactPath, pin, evidence, toolVersions);
const sbom = createSpdx(packageRoot, artifact, pin);
const manifestPath = join(output, "artifact-manifest.json");
const sbomPath = join(output, "sbom.spdx.json");
writeJson(manifestPath, manifest);
writeJson(sbomPath, sbom);
const manifestHash = fileRecord(manifestPath).sha256;
const sbomHash = fileRecord(sbomPath).sha256;
const provenance = createProvenance(artifactPath, artifact, manifestHash, sbomHash, pin, toolVersions);
const provenancePath = join(output, "provenance.intoto.json");
writeJson(provenancePath, provenance);
const records = [
[artifactPath, artifact.sha256],
[manifestPath, manifestHash],
[sbomPath, sbomHash],
[provenancePath, fileRecord(provenancePath).sha256],
].map(([path, sha256]) => ({ file: basename(path), sha256 })).sort((a, b) => a.file.localeCompare(b.file));
writeFileSync(join(output, "SHA256SUMS"), `${records.map(({ file, sha256 }) => `${sha256} ${file}`).join("\n")}\n`, { flag: "wx", mode: 0o600 });
return { artifact, evidence, files: records };
}
+560
View File
@@ -0,0 +1,560 @@
import { createHash } from "node:crypto";
import { lstatSync, readFileSync, realpathSync } from "node:fs";
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
const PI_NAME = "@earendil-works/pi-coding-agent";
const PI_VERSION = "0.85.1";
const PI_COMMIT = "d981de1229ef899957bbe968bc8dcda02a21f477";
const UNDICI_OLD = "8.9.0";
const UNDICI_FIXED = "8.12.2";
const UNDICI_URL = "https://registry.npmjs.org/undici/-/undici-8.12.2.tgz";
const UNDICI_INTEGRITY = "sha512-XtuDWVdqTLr3NAxm2ly9/hU6kUsJOgGh1RASzMBbsw6aWCOaQzHdW+QQDfdERjfJ2V5vL5pRPa4RetO9UfivDg==";
const BRACE_OLD = "5.0.9";
const BRACE_FIXED = "5.0.12";
const BRACE_URL = "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz";
const BRACE_INTEGRITY = "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==";
const PATCH_TARGETS = [
"package-lock.json",
"packages/coding-agent/npm-shrinkwrap.json",
"packages/coding-agent/package.json",
];
const ALLOWED_HOSTS = new Set(["codeload.github.com", "nodejs.org", "raw.githubusercontent.com", "registry.npmjs.org"]);
function refuse(condition, message) {
if (!condition) throw new Error(`pi rebuild recipe refused: ${message}`);
}
function at(object, path) {
return path.split(".").reduce((value, key) => value?.[key], object);
}
function expect(pin, path, expected) {
const actual = at(pin, path);
refuse(actual === expected, `${path} must be ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);
}
function expectHash(value, length, label) {
refuse(typeof value === "string" && new RegExp(`^[0-9a-f]{${length}}$`).test(value), `${label} is not a lowercase ${length}-hex digest`);
}
function validateUrl(value, expected, label) {
expect({ value }, "value", expected);
const url = new URL(value);
refuse(url.protocol === "https:", `${label} must use HTTPS`);
refuse(!url.username && !url.password && !url.hash, `${label} must not contain userinfo or a fragment`);
refuse(ALLOWED_HOSTS.has(url.hostname), `${label} host is not approved: ${url.hostname}`);
}
function json(path) {
const stat = lstatSync(path);
refuse(stat.isFile() && !stat.isSymbolicLink(), `JSON input is not a regular non-symlink file: ${path}`);
return JSON.parse(readFileSync(path, "utf8"));
}
function digest(contents, algorithm = "sha256") {
return createHash(algorithm).update(contents).digest("hex");
}
function fileDigest(path, algorithm = "sha256") {
const stat = lstatSync(path);
refuse(stat.isFile() && !stat.isSymbolicLink(), `not a regular non-symlink file: ${path}`);
return digest(readFileSync(path), algorithm);
}
function safePath(root, path) {
refuse(typeof path === "string" && path.length > 0 && !isAbsolute(path), `unsafe relative path: ${path}`);
const target = resolve(root, path);
const base = `${resolve(root)}${sep}`;
refuse(target.startsWith(base), `path escapes its root: ${path}`);
return target;
}
function validateDigestMap(root, map, label) {
refuse(map && typeof map === "object" && !Array.isArray(map), `${label} must be an object`);
for (const [path, expected] of Object.entries(map)) {
expectHash(expected, 64, `${label}.${path}`);
const actual = fileDigest(safePath(root, path));
refuse(actual === expected, `${label} sha256 preimage mismatch for ${path}: expected ${expected}, got ${actual}`);
}
}
function directUndici(lock) {
return lock?.packages?.["node_modules/undici"];
}
function directBraceExpansion(lock) {
return lock?.packages?.["node_modules/brace-expansion"];
}
function codingWorkspace(lock) {
return lock?.packages?.["packages/coding-agent"];
}
function shrinkwrapRoot(lock) {
return lock?.packages?.[""];
}
function assertUndiciResolution(entry, version, url, integrity, label) {
refuse(entry?.version === version, `${label} version must be ${version}`);
if (url !== undefined) refuse(entry?.resolved === url, `${label} resolved URL must be ${url}`);
if (integrity !== undefined) refuse(entry?.integrity === integrity, `${label} integrity must be ${integrity}`);
}
export function validatePin(pin) {
refuse(pin && typeof pin === "object" && !Array.isArray(pin), "PIN must be an object");
expect(pin, "schemaVersion", 1);
expect(pin, "state", "UNRUN");
expect(pin, "ruling", "Mos2248");
expect(pin, "pi.name", PI_NAME);
expect(pin, "pi.version", PI_VERSION);
expect(pin, "pi.tag", "v0.85.1");
expect(pin, "pi.commit", PI_COMMIT);
expect(pin, "pi.tree", "346294a615d2d0ad4f6e5fbccb4cee4ccd7b2d6c");
expect(pin, "pi.commitTimestamp", "2026-09-05T11:54:46Z");
expect(pin, "pi.sourceDateEpoch", 1788609286);
validateUrl(pin.pi?.source?.url, `https://codeload.github.com/earendil-works/pi/tar.gz/${PI_COMMIT}`, "Pi source URL");
expect(pin, "pi.source.archiveRoot", `pi-${PI_COMMIT}`);
expect(pin, "pi.source.bytes", 6978387);
expect(pin, "pi.source.sha256", "d2a04b35e57f59873d67f667f22e681e93c78a46ba8e2cdedf9d4c323b1ae8dc");
expect(pin, "pi.source.sha512", "77acb7819e75da17059cb507064739d6eaf033ad76b9a5d2b090cb607bf97ddff5a94ec0ae57ce5f1c168c95f370b6e97513e667fdb47b53ed55f486286a2ae9");
validateUrl(pin.pi?.registryReference?.metadataUrl, "https://registry.npmjs.org/@earendil-works%2fpi-coding-agent/0.85.1", "Pi registry metadata URL");
validateUrl(pin.pi?.registryReference?.tarballUrl, "https://registry.npmjs.org/@earendil-works/pi-coding-agent/-/pi-coding-agent-0.85.1.tgz", "Pi registry tarball URL");
expect(pin, "pi.registryReference.gitHead", PI_COMMIT);
expect(pin, "pi.registryReference.integrity", "sha512-FGRN+OHbWaefBPGaTggAdLjrIHW+s2PzLyglz/5dfLzb9of7uuXMXYC0fJIeZTw+shS32o2cuQ9jF7YSDuL/oQ==");
expect(pin, "pi.registryReference.tarballSha256", "1f498729649bdce647d1160993b4d92bf3c614cc819213bee2f91dd34f2a7af4");
expect(pin, "pi.registryReference.tarballSha512", "14644df8e1db59a79f04f19a4e080074b8eb2075beb363f32f2825cffe5d7cbcdbf687fbbae5cc5d80b47c921e653c3eb214b7da8d9cb90f6317b6120ee2ffa1");
validateUrl(pin.pi?.registryReference?.attestationsUrl, "https://registry.npmjs.org/-/npm/v1/attestations/@earendil-works%[email protected]", "Pi attestations URL");
expect(pin, "undici.name", "undici");
expect(pin, "undici.version", UNDICI_FIXED);
expect(pin, "undici.gitHead", "bb6cc0ae9b6827ed273ed5ba344148273629638f");
validateUrl(pin.undici?.metadata?.url, "https://registry.npmjs.org/undici/8.12.2", "Undici metadata URL");
validateUrl(pin.undici?.tarball?.url, UNDICI_URL, "Undici tarball URL");
expect(pin, "undici.tarball.bytes", 542130);
expect(pin, "undici.tarball.sha256", "a765b5e63ab5c6b1b91a59072c88f7857b36f47b09c376762241d177279cdefd");
expect(pin, "undici.tarball.sha512", "5edb8359576a4cbaf7340c66da5cbdfe153a914b093a01a1d51012ccc05bb30e9a58239a4331dd5be4100df7444637c9d95e6f2f9a513dae117ad3bd51f8af0e");
expect(pin, "undici.tarball.integrity", UNDICI_INTEGRITY);
validateUrl(pin.undici?.attestations?.url, "https://registry.npmjs.org/-/npm/v1/attestations/[email protected]", "Undici attestations URL");
expect(pin, "braceExpansion.name", "brace-expansion");
expect(pin, "braceExpansion.version", BRACE_FIXED);
expect(pin, "braceExpansion.gitHead", "f3410159d768f56c9d9f4511d3e1b46425fc1099");
validateUrl(pin.braceExpansion?.metadata?.url, "https://registry.npmjs.org/brace-expansion/5.0.12", "brace-expansion metadata URL");
expect(pin, "braceExpansion.metadata.sha256", "5421e68ea58a0286041e3f11998da4fa44055026b11c527cc14ec74895b58929");
validateUrl(pin.braceExpansion?.tarball?.url, BRACE_URL, "brace-expansion tarball URL");
expect(pin, "braceExpansion.tarball.bytes", 12620);
expect(pin, "braceExpansion.tarball.sha256", "ef8448ec78f20b692f04fa6d01f39b5ab34c66404bea3429f5a39c6c9e0be8b4");
expect(pin, "braceExpansion.tarball.sha512", "628bd0debce168b308ac38cd0cc90d4b4d6d79af77aa11211b9c72f1febe47497e770dca8bee6c0a822823de368ae2d94c75a881692d830543854d3d88d12299");
expect(pin, "braceExpansion.tarball.integrity", BRACE_INTEGRITY);
expect(pin, "toolchain.platform", "linux-x64");
expect(pin, "toolchain.node.version", "22.23.4");
validateUrl(pin.toolchain?.node?.url, "https://nodejs.org/dist/v22.23.4/node-v22.23.4-linux-x64.tar.xz", "Node archive URL");
expect(pin, "toolchain.node.sha256", "1805bcd35e6815559bd2eb97db438d3fdb0eb3a5e6a977b7c1badca76337e9ce");
validateUrl(pin.toolchain?.node?.releaseUrl, "https://nodejs.org/en/blog/release/v22.23.4", "Node release URL");
validateUrl(pin.toolchain?.node?.securityReleaseUrl, "https://nodejs.org/en/blog/vulnerability/october-2026-security-releases", "Node security release URL");
validateUrl(pin.toolchain?.node?.shasumsUrl, "https://nodejs.org/dist/v22.23.4/SHASUMS256.txt", "Node SHASUMS URL");
expect(pin, "toolchain.node.shasumsSha256", "c90c3f1478fda8308b22cc1a1b0dc536169196444e67ee8b5c582fd351d02bce");
expect(pin, "toolchain.node.pgpSignatureVerified", false);
expect(pin, "toolchain.npm.version", "10.9.9");
validateUrl(pin.toolchain?.npm?.packageJsonUrl, "https://raw.githubusercontent.com/nodejs/node/v22.23.4/deps/npm/package.json", "bundled npm package metadata URL");
expect(pin, "toolchain.npm.packageJsonSha256", "7c04bf69824d17a1db64defd3a9a86d538781bf2d6ca2764e3f1143fa5b71166");
expect(pin, "toolchain.esbuild.version", "0.28.1");
expect(pin, "patch.path", "patches/pi-0.85.1-undici-8.12.2.patch");
expect(pin, "patch.sha256", "6f1c3aaf5c6bdf08cfb02932d77251d8048130ba59c63a099b25cae26cb0bf0e");
refuse(JSON.stringify(pin.patch?.allowedTargets) === JSON.stringify(PATCH_TARGETS), "patch target allowlist changed");
expect(pin, "securityEvidence.briefSha256", "83b9462ba40e93934fcf3cc96617f59dff3950d4321e3351eb49ab83a03c1359");
expect(pin, "securityEvidence.a5ReportSha256", "71270a41a957921b5a83b09650f817fdaef31ef4e762f03a68c8eed8207fb307");
expect(pin, "securityEvidence.a5MetadataSha256", "4ae5158491a25d99778fa84cefe65260c0d6aed701b6e710de2c80ba8e39877f");
for (const [label, value] of [
["Pi metadata", pin.pi?.registryReference?.metadataSha256],
["Pi attestations", pin.pi?.registryReference?.attestationsSha256],
["Undici metadata", pin.undici?.metadata?.sha256],
["Undici attestations", pin.undici?.attestations?.sha256],
["brace-expansion metadata", pin.braceExpansion?.metadata?.sha256],
["Node SHASUMS", pin.toolchain?.node?.shasumsSha256],
["bundled npm package metadata", pin.toolchain?.npm?.packageJsonSha256],
["patch", pin.patch?.sha256],
...Object.entries(pin.sourcePreimages ?? {}).map(([path, value]) => [`sourcePreimages.${path}`, value]),
...Object.entries(pin.sourcePostimages ?? {}).map(([path, value]) => [`sourcePostimages.${path}`, value]),
...Object.entries(pin.undici?.fixedSourceFiles ?? {}).map(([path, value]) => [`fixedSourceFiles.${path}`, value]),
]) expectHash(value, 64, label);
return pin;
}
export function validatePatch(patch, pin) {
refuse(typeof patch === "string" && patch.length > 0, "patch is empty");
refuse(digest(patch) === pin.patch.sha256, `patch sha256 does not match ${pin.patch.sha256}`);
const targets = [...patch.matchAll(/^diff --git a\/(.+) b\/(.+)$/gm)].map((match) => {
refuse(match[1] === match[2], `patch renames a target: ${match[1]} -> ${match[2]}`);
return match[1];
});
refuse(JSON.stringify(targets) === JSON.stringify(pin.patch.allowedTargets), `patch targets must be exactly ${pin.patch.allowedTargets.join(", ")}`);
refuse(!patch.includes("install-lock"), "patch must not rewrite the registry install-lock");
const allowedOld = new Set([
'\t\t\t"version": "8.9.0",',
'\t\t\t"resolved": "https://registry.npmjs.org/undici/-/undici-8.9.0.tgz",',
'\t\t\t"integrity": "sha512-aWZpUj7XoGonMClx4gdDRfgBjqeA+F473aDmROQQbM9n6PRfK/u1q/a0X4wMTgcHfT8H6fpbt98PFuDUwFg2YA==",',
'\t\t\t\t"undici": "8.9.0",',
'\t\t"undici": "8.9.0",',
'\t\t\t"version": "5.0.9",',
'\t\t\t"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",',
'\t\t\t"integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",',
]);
const allowedNew = new Set([
'\t\t\t"version": "8.12.2",',
`\t\t\t"resolved": "${UNDICI_URL}",`,
`\t\t\t"integrity": "${UNDICI_INTEGRITY}",`,
'\t\t\t\t"undici": "8.12.2",',
'\t\t"undici": "8.12.2",',
'\t\t\t"version": "5.0.12",',
`\t\t\t"resolved": "${BRACE_URL}",`,
`\t\t\t"integrity": "${BRACE_INTEGRITY}",`,
]);
for (const line of patch.split("\n")) {
if (line.startsWith("---") || line.startsWith("+++")) continue;
if (line.startsWith("-")) refuse(allowedOld.has(line.slice(1)), `unapproved patch deletion: ${line}`);
if (line.startsWith("+")) refuse(allowedNew.has(line.slice(1)), `unapproved patch addition: ${line}`);
}
refuse((patch.match(/8\.9\.0/g) ?? []).length === 7, "patch must replace exactly seven 8.9.0 literals");
refuse((patch.match(/8\.12\.2/g) ?? []).length === 7, "patch must add exactly seven 8.12.2 literals");
refuse((patch.match(/5\.0\.9/g) ?? []).length === 4, "patch must replace exactly four 5.0.9 literals");
refuse((patch.match(/5\.0\.12/g) ?? []).length === 4, "patch must add exactly four 5.0.12 literals");
return { sha256: digest(patch), targets };
}
export function validateSourceTree(root, pin) {
validateDigestMap(root, pin.sourcePreimages, "source preimage");
const packageLock = json(safePath(root, "package-lock.json"));
const codingPackage = json(safePath(root, "packages/coding-agent/package.json"));
const shrinkwrap = json(safePath(root, "packages/coding-agent/npm-shrinkwrap.json"));
const bundler = readFileSync(safePath(root, "scripts/build-coding-agent-bundle.mjs"), "utf8");
refuse(codingPackage.name === PI_NAME && codingPackage.version === PI_VERSION, "source coding-agent identity changed");
refuse(codingPackage.bin?.pi === "dist/bundle/cli.js", "source Pi bin is not the bundled CLI");
refuse(codingPackage.dependencies?.undici === UNDICI_OLD, `source coding-agent must start at Undici ${UNDICI_OLD}`);
assertUndiciResolution(directUndici(packageLock), UNDICI_OLD, "https://registry.npmjs.org/undici/-/undici-8.9.0.tgz", undefined, "source package-lock Undici");
refuse(codingWorkspace(packageLock)?.dependencies?.undici === UNDICI_OLD, "source workspace lock Undici changed");
assertUndiciResolution(directUndici(shrinkwrap), UNDICI_OLD, "https://registry.npmjs.org/undici/-/undici-8.9.0.tgz", undefined, "source shrinkwrap Undici");
refuse(shrinkwrapRoot(shrinkwrap)?.dependencies?.undici === UNDICI_OLD, "source shrinkwrap root Undici changed");
assertUndiciResolution(directBraceExpansion(packageLock), BRACE_OLD, "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", undefined, "source package-lock brace-expansion");
assertUndiciResolution(directBraceExpansion(shrinkwrap), BRACE_OLD, "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", undefined, "source shrinkwrap brace-expansion");
refuse(packageLock.packages?.["node_modules/@earendil-works/gondolin/node_modules/undici"]?.version === "6.28.0", "unrelated Gondolin Undici pin changed");
refuse(/bundle:\s*true/.test(bundler), "upstream coding-agent build no longer bundles dependencies");
const externalBlock = bundler.match(/allowedExternalPackages\s*=\s*new Set\(\[([\s\S]*?)\]\)/)?.[1] ?? "";
refuse(externalBlock.length > 0 && !/["']undici(?:\/[^"']*)?["']/.test(externalBlock), "Undici is externalized or the bundler allowlist is unreadable");
if (pin.sourcePreimages["packages/coding-agent/install-lock/package-lock.json"]) {
const installLock = json(safePath(root, "packages/coding-agent/install-lock/package-lock.json"));
assertUndiciResolution(directUndici(installLock), UNDICI_OLD, "https://registry.npmjs.org/undici/-/undici-8.9.0.tgz", undefined, "published-registry install-lock Undici");
assertUndiciResolution(directBraceExpansion(installLock), BRACE_OLD, "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", undefined, "published-registry install-lock brace-expansion");
}
return { piVersion: codingPackage.version, undiciVersion: codingPackage.dependencies.undici, braceExpansionVersion: directBraceExpansion(shrinkwrap).version, bin: codingPackage.bin.pi };
}
export function validatePatchedTree(root, pin) {
validateDigestMap(root, pin.sourcePostimages, "source postimage");
const packageLock = json(safePath(root, "package-lock.json"));
const codingPackage = json(safePath(root, "packages/coding-agent/package.json"));
const shrinkwrap = json(safePath(root, "packages/coding-agent/npm-shrinkwrap.json"));
refuse(codingPackage.name === PI_NAME && codingPackage.version === PI_VERSION, "patched coding-agent identity changed");
refuse(codingPackage.bin?.pi === "dist/bundle/cli.js", "patched Pi bin is not the bundled CLI");
refuse(codingPackage.dependencies?.undici === UNDICI_FIXED, `patched coding-agent must use Undici ${UNDICI_FIXED}`);
assertUndiciResolution(directUndici(packageLock), UNDICI_FIXED, UNDICI_URL, UNDICI_INTEGRITY, "patched package-lock Undici");
refuse(codingWorkspace(packageLock)?.dependencies?.undici === UNDICI_FIXED, "patched workspace lock Undici changed");
assertUndiciResolution(directUndici(shrinkwrap), UNDICI_FIXED, UNDICI_URL, UNDICI_INTEGRITY, "patched shrinkwrap Undici");
refuse(shrinkwrapRoot(shrinkwrap)?.dependencies?.undici === UNDICI_FIXED, "patched shrinkwrap root Undici changed");
assertUndiciResolution(directBraceExpansion(packageLock), BRACE_FIXED, BRACE_URL, BRACE_INTEGRITY, "patched package-lock brace-expansion");
assertUndiciResolution(directBraceExpansion(shrinkwrap), BRACE_FIXED, BRACE_URL, BRACE_INTEGRITY, "patched shrinkwrap brace-expansion");
refuse(packageLock.packages?.["node_modules/@earendil-works/gondolin/node_modules/undici"]?.version === "6.28.0", "unrelated Gondolin Undici pin changed");
const installLock = json(safePath(root, "packages/coding-agent/install-lock/package-lock.json"));
assertUndiciResolution(directUndici(installLock), UNDICI_OLD, "https://registry.npmjs.org/undici/-/undici-8.9.0.tgz", undefined, "unchanged published-registry install-lock Undici");
assertUndiciResolution(directBraceExpansion(installLock), BRACE_OLD, "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", undefined, "unchanged published-registry install-lock brace-expansion");
return { piVersion: codingPackage.version, undiciVersion: codingPackage.dependencies.undici, braceExpansionVersion: directBraceExpansion(shrinkwrap).version, bin: codingPackage.bin.pi };
}
export function assertInstalledUndici(root, pin) {
const manifest = json(safePath(root, "package.json"));
refuse(manifest.name === "undici" && manifest.version === UNDICI_FIXED, `installed Undici must be exactly ${UNDICI_FIXED}`);
for (const [path, expected] of Object.entries(pin.undici.fixedSourceFiles)) {
const actual = fileDigest(safePath(root, path));
refuse(actual === expected, `installed Undici fixed-source hash mismatch for ${path}`);
}
return { version: manifest.version, sourceFiles: pin.undici.fixedSourceFiles };
}
function quoted(contents, start) {
const quote = contents[start];
let value = "";
for (let index = start + 1; index < contents.length; index++) {
const character = contents[index];
if (character === "\\") {
refuse(index + 1 < contents.length, "unterminated escape in bundled JavaScript");
value += contents[index + 1];
index++;
continue;
}
if (character === quote) return { next: index + 1, value };
value += character;
}
throw new Error(`pi rebuild recipe refused: unterminated string at byte ${start} in bundled JavaScript near ${JSON.stringify(contents.slice(Math.max(0, start - 80), start + 160))}`);
}
function skipTrivia(contents, start) {
let index = start;
while (index < contents.length) {
if (/\s/.test(contents[index])) {
index++;
continue;
}
if (contents.startsWith("//", index)) {
const end = contents.indexOf("\n", index + 2);
index = end < 0 ? contents.length : end + 1;
continue;
}
if (contents.startsWith("/*", index)) {
const end = contents.indexOf("*/", index + 2);
refuse(end >= 0, "unterminated block comment in bundled JavaScript");
index = end + 2;
continue;
}
break;
}
return index;
}
function identifierAt(contents, start) {
if (!/[A-Za-z_$]/.test(contents[start] ?? "")) return undefined;
let end = start + 1;
while (/[A-Za-z0-9_$]/.test(contents[end] ?? "")) end++;
return { end, value: contents.slice(start, end) };
}
function importClauseSpecifier(contents, start) {
let index = start;
while (index < contents.length) {
index = skipTrivia(contents, index);
if (contents[index] === ";") return undefined;
if (contents[index] === '"' || contents[index] === "'") {
index = quoted(contents, index).next;
continue;
}
const identifier = identifierAt(contents, index);
if (identifier) {
index = identifier.end;
if (identifier.value !== "from") continue;
index = skipTrivia(contents, index);
refuse(contents[index] === '"' || contents[index] === "'", "module from-clause has no literal specifier");
return quoted(contents, index);
}
index++;
}
return undefined;
}
function regexLiteralEnd(contents, start) {
let characterClass = false;
for (let index = start + 1; index < contents.length; index++) {
const character = contents[index];
if (character === "\\") {
index++;
continue;
}
if (character === "[") {
characterClass = true;
continue;
}
if (character === "]") {
characterClass = false;
continue;
}
if (character === "/" && !characterClass) {
index++;
while (/[A-Za-z]/.test(contents[index] ?? "")) index++;
return index;
}
refuse(character !== "\n" && character !== "\r", `unterminated regex literal at byte ${start} in bundled JavaScript`);
}
throw new Error(`pi rebuild recipe refused: unterminated regex literal at byte ${start} in bundled JavaScript`);
}
function importSpecifiers(contents) {
const found = new Set();
const regexPrefixKeywords = new Set([
"await", "case", "delete", "do", "else", "in", "instanceof", "new", "of", "return", "throw", "typeof", "void", "yield",
]);
let index = 0;
let canStartRegex = true;
while (index < contents.length) {
index = skipTrivia(contents, index);
const character = contents[index];
if (character === '"' || character === "'" || character === "`") {
index = quoted(contents, index).next;
canStartRegex = false;
continue;
}
if (character === "/") {
if (canStartRegex) {
index = regexLiteralEnd(contents, index);
canStartRegex = false;
} else {
index += contents[index + 1] === "=" ? 2 : 1;
canStartRegex = true;
}
continue;
}
if (/[0-9]/.test(character ?? "")) {
index++;
while (/[A-Za-z0-9_.]/.test(contents[index] ?? "")) index++;
canStartRegex = false;
continue;
}
const identifier = identifierAt(contents, index);
if (!identifier) {
if ((character === "+" || character === "-") && contents[index + 1] === character) {
index += 2;
canStartRegex = false;
continue;
}
canStartRegex = !")]}".includes(character) && character !== ".";
index++;
continue;
}
index = identifier.end;
if (identifier.value !== "import" && identifier.value !== "export") {
canStartRegex = regexPrefixKeywords.has(identifier.value);
continue;
}
index = skipTrivia(contents, index);
if (identifier.value === "import" && contents[index] === ".") {
canStartRegex = false;
continue;
}
if (identifier.value === "import" && contents[index] === "(") {
index = skipTrivia(contents, index + 1);
if (contents[index] === '"' || contents[index] === "'") {
const specifier = quoted(contents, index);
found.add(specifier.value);
index = specifier.next;
}
canStartRegex = false;
continue;
}
if (identifier.value === "import" && (contents[index] === '"' || contents[index] === "'")) {
const specifier = quoted(contents, index);
found.add(specifier.value);
index = specifier.next;
canStartRegex = false;
continue;
}
const specifier = importClauseSpecifier(contents, index);
if (specifier) {
found.add(specifier.value);
index = specifier.next;
}
canStartRegex = false;
}
return found;
}
function reachableBundle(packageRoot, entry) {
const bundleRoot = realpathSync(safePath(packageRoot, "dist/bundle"));
const entryPath = safePath(packageRoot, entry);
const entryStat = lstatSync(entryPath);
refuse(entryStat.isFile() && !entryStat.isSymbolicLink(), `bundle entry is not a regular non-symlink file: ${entry}`);
const queue = [realpathSync(entryPath)];
const files = new Map();
const external = new Set();
while (queue.length > 0) {
const file = queue.shift();
refuse(file === bundleRoot || file.startsWith(`${bundleRoot}${sep}`), `bundle import escapes dist/bundle: ${file}`);
if (files.has(file)) continue;
const contents = readFileSync(file, "utf8");
files.set(file, contents);
let specifiers;
try {
specifiers = importSpecifiers(contents);
} catch (error) {
throw new Error(`${error.message}; bundle file ${relative(packageRoot, file).split(sep).join("/")}`);
}
for (const specifier of specifiers) {
if (!specifier.startsWith(".")) {
external.add(specifier);
continue;
}
const unresolved = resolve(dirname(file), specifier);
const targetStat = lstatSync(unresolved);
refuse(targetStat.isFile() && !targetStat.isSymbolicLink(), `bundle import is not a regular non-symlink file: ${specifier}`);
const target = realpathSync(unresolved);
refuse(target.startsWith(`${bundleRoot}${sep}`), `relative bundle import escapes dist/bundle: ${specifier}`);
queue.push(target);
}
}
return { files, external };
}
export function inspectBundleClosure(packageRoot, entry = "dist/bundle/cli.js") {
const closure = reachableBundle(packageRoot, entry);
return {
reachableFiles: [...closure.files.keys()]
.map((path) => relative(packageRoot, path).split(sep).join("/"))
.sort(),
externalImports: [...closure.external].sort(),
};
}
export function assertBundleConsumption(packageRoot, pin, options = {}) {
const manifest = json(safePath(packageRoot, "package.json"));
const shrinkwrap = json(safePath(packageRoot, "npm-shrinkwrap.json"));
refuse(manifest.name === PI_NAME && manifest.version === PI_VERSION, `artifact identity must remain ${PI_NAME}@${PI_VERSION}`);
refuse(manifest.bin?.pi === "dist/bundle/cli.js", "artifact Pi bin does not consume dist/bundle/cli.js");
assertUndiciResolution(directUndici(shrinkwrap), UNDICI_FIXED, UNDICI_URL, UNDICI_INTEGRITY, "artifact shrinkwrap Undici");
refuse(shrinkwrapRoot(shrinkwrap)?.dependencies?.undici === UNDICI_FIXED, "artifact shrinkwrap root does not declare Undici 8.12.2");
assertUndiciResolution(directBraceExpansion(shrinkwrap), BRACE_FIXED, BRACE_URL, BRACE_INTEGRITY, "artifact shrinkwrap brace-expansion");
const installedUndici = options.installedUndiciRoot ? assertInstalledUndici(options.installedUndiciRoot, pin) : undefined;
const closure = reachableBundle(packageRoot, manifest.bin.pi);
refuse(![...closure.external].some((specifier) => specifier === "undici" || specifier.startsWith("undici/")), "bundle leaves Undici external");
const joined = [...closure.files.values()].join("\n");
const fixedContentEncoding = /controller\.abort\(new Error\(`too many content-encodings in response:/.test(joined);
const vulnerableContentEncoding = /reject\(new Error\(`too many content-encodings in response:/.test(joined);
// This is a conservative signature check for the direct assertion forms in
// Undici's affected source and bundles, not a proof of arbitrary JS semantics.
const vulnerableTlsAssertion = /\bassert\s*\(\s*[^);\n]*?\.code\s*!==\s*(["'])ERR_TLS_CERT_ALTNAME_INVALID\1/.test(joined);
refuse(fixedContentEncoding, "fixed content-encoding abort signature is absent from the reachable CLI bundle");
refuse(!vulnerableContentEncoding, "vulnerable content-encoding reject signature remains in the reachable CLI bundle");
refuse(!vulnerableTlsAssertion, "vulnerable TLS assertion signature remains in the reachable CLI bundle");
const reachableFiles = [...closure.files.keys()]
.map((path) => relative(packageRoot, path).split(sep).join("/"))
.sort();
return {
piVersion: manifest.version,
undiciVersion: directUndici(shrinkwrap).version,
braceExpansionVersion: directBraceExpansion(shrinkwrap).version,
entry: manifest.bin.pi,
reachableFiles,
externalImports: [...closure.external].sort(),
fixedSignatures: {
contentEncodingAbort: true,
vulnerableContentEncodingRejectAbsent: true,
knownDirectVulnerableTlsAssertionSignaturesAbsent: true,
},
...(installedUndici ? { installedUndici } : {}),
};
}
export const constants = Object.freeze({
piName: PI_NAME,
piVersion: PI_VERSION,
piCommit: PI_COMMIT,
undiciVersion: UNDICI_FIXED,
undiciUrl: UNDICI_URL,
undiciIntegrity: UNDICI_INTEGRITY,
braceExpansionVersion: BRACE_FIXED,
braceExpansionUrl: BRACE_URL,
braceExpansionIntegrity: BRACE_INTEGRITY,
});
+169
View File
@@ -0,0 +1,169 @@
import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { mkdtempSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { join, resolve } from "node:path";
import test from "node:test";
import {
assertBundleConsumption,
validatePatch,
validatePin,
validateSourceTree,
} from "../src/recipe.mjs";
import { writeReceipts } from "../src/receipts.mjs";
const PACKAGE_ROOT = resolve(import.meta.dirname, "..");
const PIN = JSON.parse(readFileSync(join(PACKAGE_ROOT, "PIN.json"), "utf8"));
const PATCH = readFileSync(join(PACKAGE_ROOT, PIN.patch.path), "utf8");
const TEST_SCRATCH = process.env.MOSAIC_PI_REBUILD_TEST_TMP;
function scratch(t) {
assert.ok(TEST_SCRATCH, "MOSAIC_PI_REBUILD_TEST_TMP must name the owned mktemp scratch directory");
const directory = mkdtempSync(join(TEST_SCRATCH, "fixture-"));
t.after(() => rmSync(directory, { force: true, recursive: true }));
return directory;
}
function sha256(text) {
return createHash("sha256").update(text).digest("hex");
}
function write(directory, path, contents) {
const target = join(directory, path);
mkdirSync(resolve(target, ".."), { recursive: true });
writeFileSync(target, contents);
}
test("the exact source, toolchain, Undici and three-file patch pins fail closed", () => {
assert.doesNotThrow(() => validatePin(PIN));
assert.equal(PIN.braceExpansion.version, "5.0.12", "full A5 tree requirement");
assert.doesNotThrow(() => validatePatch(PATCH, PIN));
for (const mutate of [
(pin) => { pin.pi.version = "0.85.2"; },
(pin) => { pin.pi.commit = "0".repeat(40); },
(pin) => { pin.undici.version = "8.12.3"; },
(pin) => { pin.undici.tarball.sha256 = "0".repeat(64); },
(pin) => { pin.braceExpansion.version = "5.0.13"; },
(pin) => { pin.toolchain.node.version = "22.23.3"; },
(pin) => { pin.pi.source.url = "https://[email protected]/pi.tar.gz"; },
]) {
const changed = structuredClone(PIN);
mutate(changed);
assert.throws(() => validatePin(changed));
}
assert.throws(
() => validatePatch(`${PATCH}\ndiff --git a/package.json b/package.json\n`, PIN),
/sha256|package\.json|allowed|target/i,
);
assert.throws(
() => validatePatch(PATCH.replace("packages/coding-agent/npm-shrinkwrap.json", "packages/coding-agent/install-lock/package-lock.json"), PIN),
/sha256|install-lock|allowed|target/i,
);
});
test("source validation binds preimage bytes and the existing bundled consumer graph", (t) => {
const root = scratch(t);
const files = {
"package-lock.json": `${JSON.stringify({ packages: {
"node_modules/brace-expansion": {
version: "5.0.9",
resolved: "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
},
"node_modules/undici": {
version: "8.9.0",
resolved: "https://registry.npmjs.org/undici/-/undici-8.9.0.tgz",
integrity: "old-integrity",
},
"packages/coding-agent": { dependencies: { undici: "8.9.0" } },
"node_modules/@earendil-works/gondolin/node_modules/undici": { version: "6.28.0" },
} }, null, "\t")}\n`,
"packages/coding-agent/package.json": `${JSON.stringify({
name: "@earendil-works/pi-coding-agent",
version: "0.85.1",
bin: { pi: "dist/bundle/cli.js" },
dependencies: { undici: "8.9.0" },
}, null, "\t")}\n`,
"packages/coding-agent/npm-shrinkwrap.json": `${JSON.stringify({ packages: {
"": { version: "0.85.1", dependencies: { undici: "8.9.0" } },
"node_modules/brace-expansion": { version: "5.0.9", resolved: "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", integrity: "old-brace-integrity" },
"node_modules/undici": { version: "8.9.0", resolved: "https://registry.npmjs.org/undici/-/undici-8.9.0.tgz", integrity: "old-integrity" },
} }, null, "\t")}\n`,
"scripts/build-coding-agent-bundle.mjs": "const allowedExternalPackages = new Set(['jiti']); const options = { bundle: true };\n",
};
for (const [path, contents] of Object.entries(files)) write(root, path, contents);
const preimages = Object.fromEntries(Object.entries(files).map(([path, contents]) => [path, sha256(contents)]));
assert.doesNotThrow(() => validateSourceTree(root, { ...PIN, sourcePreimages: preimages }));
write(root, "packages/coding-agent/package.json", files["packages/coding-agent/package.json"].replace("8.9.0", "8.12.2"));
assert.throws(() => validateSourceTree(root, { ...PIN, sourcePreimages: preimages }), /preimage|sha256/i);
});
test("bundle assertion fixture requires the fixed signatures in the reachable CLI closure", (t) => {
const root = scratch(t);
const packageRoot = join(root, "package");
write(packageRoot, "package.json", `${JSON.stringify({
name: "@earendil-works/pi-coding-agent",
version: "0.85.1",
bin: { pi: "dist/bundle/cli.js" },
}, null, "\t")}\n`);
write(packageRoot, "npm-shrinkwrap.json", `${JSON.stringify({ packages: {
"": { version: "0.85.1", dependencies: { undici: "8.12.2" } },
"node_modules/brace-expansion": {
version: "5.0.12",
resolved: PIN.braceExpansion.tarball.url,
integrity: PIN.braceExpansion.tarball.integrity,
},
"node_modules/undici": {
version: "8.12.2",
resolved: PIN.undici.tarball.url,
integrity: PIN.undici.tarball.integrity,
},
} }, null, "\t")}\n`);
write(packageRoot, "dist/bundle/cli.js", 'import "./chunks/fixed.js";\n');
const fixedBundle = [
"const documentation=\"pass the fetch implementation from the same undici package: import { fetch, ProxyAgent } from 'undici';\";",
"const syntaxPattern=/from[\"']undici/;",
"function fixed(controller,codings,maxContentEncodings){",
"if(codings.length>maxContentEncodings){controller.abort(new Error(`too many content-encodings in response: ${codings.length}, maximum allowed is ${maxContentEncodings}`));return}",
"}",
"function handled(err2){return err2.code===\"ERR_TLS_CERT_ALTNAME_INVALID\"}",
].join("");
write(packageRoot, "dist/bundle/chunks/fixed.js", fixedBundle);
const evidence = assertBundleConsumption(packageRoot, PIN);
assert.deepEqual(evidence.reachableFiles, ["dist/bundle/chunks/fixed.js", "dist/bundle/cli.js"]);
assert.ok(!evidence.externalImports.includes("undici"), "documentation text is not an import");
assert.equal(evidence.undiciVersion, "8.12.2");
const artifact = join(root, "fixture-artifact.tgz");
writeFileSync(artifact, "fixture only; not a Pi artifact\n");
for (const outputDirectory of [join(root, "receipts-a"), join(root, "receipts-b")]) {
writeReceipts({
artifactPath: artifact,
packageRoot,
outputDirectory,
pin: PIN,
toolVersions: { node: "v22.23.4", npm: "10.9.9" },
});
}
assert.deepEqual(readdirSync(join(root, "receipts-a")).sort(), readdirSync(join(root, "receipts-b")).sort());
for (const file of readdirSync(join(root, "receipts-a"))) {
assert.deepEqual(readFileSync(join(root, "receipts-a", file)), readFileSync(join(root, "receipts-b", file)), file);
}
// Keep the fixed-abort positive present so only the TLS guard can refuse.
for (const assertion of [
'assert(err.code!=="ERR_TLS_CERT_ALTNAME_INVALID")',
'assert(err.code !== "ERR_TLS_CERT_ALTNAME_INVALID")',
"assert (err.code !== 'ERR_TLS_CERT_ALTNAME_INVALID')",
]) {
write(packageRoot, "dist/bundle/chunks/fixed.js", `${fixedBundle};${assertion};`);
assert.throws(() => assertBundleConsumption(packageRoot, PIN), /vulnerable TLS assertion signature/);
}
write(packageRoot, "dist/bundle/chunks/fixed.js", `import"undici";${fixedBundle}`);
assert.throws(() => assertBundleConsumption(packageRoot, PIN), /leaves Undici external/i);
write(packageRoot, "dist/bundle/chunks/fixed.js", "function vulnerable(reject,err2){reject(new Error(`too many content-encodings in response:`));assert(err2.code!==\"ERR_TLS_CERT_ALTNAME_INVALID\")}\n");
assert.throws(() => assertBundleConsumption(packageRoot, PIN), /fixed content-encoding|TLS assertion|signature/i);
});