-
Notifications
You must be signed in to change notification settings - Fork 1
Verify vendored pgstencil provenance and defer its code audit to pgstencil #745
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -33,6 +33,40 @@ const run = (command, args, cwd, env = process.env) => | |
| execFileSync(command, args, { cwd, stdio: "inherit", env }); | ||
| const git = (...args) => | ||
| execFileSync("git", args, { cwd: repository, encoding: "utf8" }).trim(); | ||
| const succeeds = (attempt) => { | ||
| try { | ||
| attempt(); | ||
| return true; | ||
| } catch { | ||
| return false; | ||
| } | ||
| }; | ||
| // Every pgstencil pack writes package/dist/provenance.json: the archive's own | ||
| // claim of the commit it came from, plus `dirty` when it was packed | ||
| // --allow-dirty. Dormouse verifies that claim instead of auditing the packed | ||
| // code, which pgstencil audits in its own repository — | ||
| // docs/specs/security-hosted.md -> "Deployment boundary". | ||
| const provenanceOf = (archive) => { | ||
| let raw; | ||
| try { | ||
| raw = execFileSync( | ||
| "tar", | ||
| ["-xOf", archive, "package/dist/provenance.json"], | ||
| { encoding: "utf8" }, | ||
| ); | ||
| } catch { | ||
| throw new Error(`${archive} carries no package/dist/provenance.json`); | ||
| } | ||
| let claim; | ||
| try { | ||
| claim = JSON.parse(raw); | ||
| } catch { | ||
| throw new Error(`${archive}: package/dist/provenance.json is not JSON`); | ||
| } | ||
| if (typeof claim?.commit !== "string" || !/^[0-9a-f]{40}$/.test(claim.commit)) | ||
| throw new Error(`${archive}: provenance.json names no commit`); | ||
| return claim; | ||
| }; | ||
| const manifest = JSON.parse( | ||
| readFileSync(resolve(root, "hosted/package.json"), "utf8"), | ||
| ); | ||
|
|
@@ -56,6 +90,9 @@ const overrides = new Map( | |
| // --working-tree packs the checkout as it stands, for trying unfinished | ||
| // pgstencil changes. That result depends on local state even when git status is | ||
| // clean, so it is always recorded dirty and production preflight refuses it. | ||
| // Either way `commit` is the HEAD of the checkout that was packed, so it is | ||
| // what each archive's own provenance must name; a pack that reports itself | ||
| // dirty (pgstencil's --allow-dirty) is refused below rather than vendored. | ||
| const commit = git( | ||
| "rev-parse", | ||
| "--verify", | ||
|
|
@@ -92,11 +129,22 @@ try { | |
| run("pnpm", ["install", "--frozen-lockfile"], checkout, pgstencil); | ||
| run("pnpm", ["packages:pack"], checkout, pgstencil); | ||
| mkdirSync(resolve(root, "vendor"), { recursive: true }); | ||
| for (const filename of archives) | ||
| copyFileSync( | ||
| resolve(checkout, "dist/packages", filename), | ||
| resolve(root, "vendor", filename), | ||
| ); | ||
| for (const filename of archives) { | ||
| const target = resolve(root, "vendor", filename); | ||
| copyFileSync(resolve(checkout, "dist/packages", filename), target); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The archive is written into Reading the claim from for (const filename of archives) {
// Both archives are held to the commit packed here, so a stale pack —
// or two archives disagreeing with each other — stops the sync before
// build.json or vendor/ can hold a commit the bytes do not carry.
const claim = provenanceOf(resolve(checkout, "dist/packages", filename));
if (claim.dirty === true)
throw new Error(
`${filename} was packed --allow-dirty; a dirty pack cannot be vendored`,
);
if (claim.commit !== commit)
throw new Error(
`${filename} was packed from ${claim.commit}, not the ${commit} packed here`,
);
}
for (const filename of archives)
copyFileSync(
resolve(checkout, "dist/packages", filename),
resolve(root, "vendor", filename),
);Left as prose rather than a suggestion block because the edit spans the whole loop. |
||
| // Both archives are held to the commit packed here, so a stale pack — | ||
| // or two archives disagreeing with each other — stops the sync before | ||
| // build.json can record a commit the bytes do not carry. | ||
| const claim = provenanceOf(target); | ||
| if (claim.dirty === true) | ||
| throw new Error( | ||
| `${filename} was packed --allow-dirty; a dirty pack cannot be vendored`, | ||
| ); | ||
| if (claim.commit !== commit) | ||
| throw new Error( | ||
| `${filename} was packed from ${claim.commit}, not the ${commit} packed here`, | ||
| ); | ||
| } | ||
| } finally { | ||
| if (!workingTree) git("worktree", "remove", "--force", checkout); | ||
| } | ||
|
|
@@ -117,3 +165,17 @@ run( | |
| ["--filter", "dormouse-hosted", "update", "pgstencil", "@pgstencil/auth"], | ||
| root, | ||
| ); | ||
| // Dormouse `main` must vendor a pgstencil `main` commit, because the nightly | ||
| // audit reads that commit's pgstencil `security-audit` check run — but a | ||
| // Dormouse branch may vendor a pgstencil branch while a cross-repo change is | ||
| // in flight, so this warns and does not fail. | ||
| const fetched = succeeds(() => git("fetch", "origin", "main")); | ||
| const onMain = | ||
| fetched && | ||
| succeeds(() => git("merge-base", "--is-ancestor", commit, "origin/main")); | ||
| if (!onMain) | ||
| console.warn( | ||
| fetched | ||
| ? `warning: pgstencil ${commit} is not on origin/main — Dormouse main must vendor a pgstencil main commit, and the nightly audit fails until it does` | ||
| : `warning: could not fetch pgstencil origin/main to check ${commit} — Dormouse main must vendor a pgstencil main commit`, | ||
| ); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,14 +1,14 @@ | ||
| { | ||
| "commit": "297edf6590e61200857b199d69160f0567bbb3c8", | ||
| "commit": "ddbf96c828c3e6a1129ff7601afeee2e47f80172", | ||
| "dirty": false, | ||
| "files": [ | ||
| { | ||
| "filename": "pgstencil-0.1.0.tgz", | ||
| "sha256": "5ed674d0c62619a2eaa09c40ea447b730670b92ba3e32ae784755b70ed8f2c7e" | ||
| "sha256": "f9210dfabe12719c0907e797e3bf5e3cbbcd5b9825f5a53343d493bb1aae895f" | ||
| }, | ||
| { | ||
| "filename": "pgstencil-auth-0.1.0.tgz", | ||
| "sha256": "7ada0c3d2031b3d06bec00c623c4f339d4045fd63b1f7a7fe371bd40ca7b7baa" | ||
| "sha256": "4671664cb76ec8cbfcd9d19e1d40774971aef58a8b05aaa9af48c3e741805b93" | ||
| } | ||
| ] | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
--working-treerecordsdirty: trueinbuild.jsonby design (const dirty = workingTree;inscripts/sync-pgstencil.mjs), even when the pgstencil checkout is clean — so this assertion makespnpm --filter dormouse-hosted testfail for every--working-treesync.hosted/README.mdsays to re-run the integration tests after every refresh, which that mode can no longer pass.verifyPackagesalready refuses a dirtybuild.json, and it is the gate the README anddocs/specs/hosted.md-> "Production releases" both name as owning this. Dropping the assertion keeps the three in agreement; if you'd rather the hosted suite refuse it too, the README's--working-treeparagraph is the thing that needs to change.