import { spawn, spawnSync, type ChildProcess } from "node:child_process"; import { createHash, generateKeyPairSync, sign } from "node:crypto"; import { constants, readFileSync } from "node:fs"; import { cp, link, lstat, mkdir, mkdtemp, open, readFile, readdir, rename, rm, stat, symlink, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; import { setTimeout as delay } from "node:timers/promises"; import { afterAll, afterEach, describe, expect, it } from "vitest"; import type { CiGateArtifact, CiGateArtifactSchema, } from "../../scripts/contracts/ci-gates.ts"; import { parseCiGateContract } from "../../scripts/contracts/ci-gates.ts"; import { hasCiArtifactSemanticValidator, readBoundedRegularFile, validateCiArtifact, } from "../../scripts/lib/ci-artifact-validator.ts"; import { writeCiGateLogAtomic } from "../../scripts/lib/ci-gate-log.ts"; import { linkFixtureNodeModules } from "../../scripts/lib/fixture-node-modules.ts"; import { copyReleaseEvidenceTree } from "../../scripts/lib/removal-fixture.ts"; import { captureCiCandidateArchive, verifyCiCandidateArchive } from "../../scripts/lib/ci-candidate-archive.ts"; import { CANDIDATE_ARCHIVE_USAGE, parseCandidateArchiveArguments, } from "../../scripts/lib/ci-candidate-archive-cli.ts"; import { validateProviderUpload } from "../../scripts/lib/provider-upload-validator.ts"; import { verifyExactPromotionBundle } from "../../scripts/lib/exact-promotion-bundle.ts"; import { cleanupFinalizedPromotion, stageVerifiedPromotion, } from "../../scripts/lib/promotion-stager.ts"; import { PROMOTED_FILE_NAMES } from "../../scripts/contracts/promotion-artifacts.ts"; import { providerEvidenceSignaturePayload, providerPublicKeyFingerprint, providerVerificationArtifactSchema, } from "../../scripts/lib/provider-evidence.ts"; import { localEvidenceAssessmentArtifactSchema } from "../../scripts/contracts/release-artifacts.ts"; import { readProviderTrust } from "../../scripts/lib/provider-trust.ts"; import { createReleaseCandidateManifest, LOCAL_EVIDENCE_ASSESSMENT_PATH, RELEASE_CANDIDATE_EVIDENCE_PATHS, RELEASE_CANDIDATE_MANIFEST_PATH, } from "../../scripts/lib/release-candidate.ts"; /** * Budget for the provider suites specifically. They spawn a systemd scope, a * bubblewrap sandbox and a signing provider, and build a release candidate to * do it; the 10s default is sized for pure-JS unit tests. Raising the global * default instead would hide a genuinely hung test. */ const PROCESS_HEAVY_TIMEOUT_MS = 30_000; const temporaryRoots: string[] = []; let providerBaseRoot: string | undefined; const sha256 = (value: Buffer | string) => createHash("sha256").update(value).digest("hex"); afterEach(async () => { await Promise.all( temporaryRoots.splice(0).map((root) => rm(root, { recursive: true, force: true })), ); }); afterAll(async () => { if (providerBaseRoot) { await rm(providerBaseRoot, { recursive: true, force: true }); } }); async function temporaryRoot(prefix: string): Promise { const root = await mkdtemp(path.join(tmpdir(), prefix)); temporaryRoots.push(root); return root; } async function writeArtifact(root: string, relative: string, value: string | Buffer) { await mkdir(path.dirname(path.join(root, relative)), { recursive: true }); await writeFile(path.join(root, relative), value); } function artifact(pathname: string, schemaId: string): CiGateArtifact { return { id: `artifact-${schemaId}`, path: pathname, schemaId, production: "source-controlled", }; } describe("CI artifact validator", () => { it("does not create directories through a pre-existing log ancestor symlink", async () => { const root = await temporaryRoot("ci-log-root-"); const outside = await temporaryRoot("ci-log-outside-"); await symlink(outside, path.join(root, "linked")); await expect( writeCiGateLogAtomic({ root, relativePath: "linked/new/report.txt", content: "blocked\n", }), ).rejects.toThrow(/ancestor is unsafe/i); await expect( import("node:fs/promises").then(({ lstat }) => lstat(path.join(outside, "new"))), ).rejects.toMatchObject({ code: "ENOENT" }); }); it.each([ ["report.txt", { id: "text", kind: "text", maxBytes: 1_024 }, "gate output\n"], ["report.xml", { id: "junit", kind: "junit", maxBytes: 1_024 }, '\n'], ["report.html", { id: "html", kind: "html", maxBytes: 1_024 }, "\n"], ["report.md", { id: "markdown", kind: "markdown", maxBytes: 1_024 }, "# Review\n"], ["schema.json", { id: "json-schema", kind: "json-schema", maxBytes: 1_024 }, '{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object"}\n'], ["scan.sarif", { id: "sarif", kind: "sarif", maxBytes: 4_096 }, JSON.stringify({ version: "2.1.0", $schema: "https://json.schemastore.org/sarif-2.1.0.json", runs: [{ tool: { driver: { name: "ca-frontend-secret-scan", rules: [] } }, results: [] }] })], ] as const)("accepts a valid %s artifact", async (relative, schema, content) => { const root = await temporaryRoot("ci-artifact-kind-"); await writeArtifact(root, relative, content); await expect( validateCiArtifact({ root, artifact: artifact(relative, schema.id), schema: schema as CiGateArtifactSchema, }), ).resolves.toBeUndefined(); }); it("accepts schema-valid negative evidence without treating status as command authority", async () => { const root = await temporaryRoot("ci-artifact-negative-"); const relative = "negative.json"; await writeArtifact( root, relative, `${JSON.stringify({ schemaVersion: 2, sourceRoot: "src", status: "FAIL", facts: { scannedFiles: 0, visualBaselines: 0, sharedScenarios: 0, declaredScenarioExecutions: 0, executedScenarioExecutions: 0 }, failures: ["fixture"] })}\n`, ); await expect( validateCiArtifact({ root, artifact: artifact(relative, "test-evidence"), schema: { id: "test-evidence", kind: "json", maxBytes: 4_096, executableSchemaId: "test-evidence-report", }, }), ).resolves.toBeUndefined(); }); const invalidFixtures: ReadonlyArray< readonly [string, (root: string) => Promise, RegExp] > = [ ["missing", async (_root: string): Promise => undefined, /not a regular file|ENOENT/i], ["empty", async (root: string): Promise => { await writeArtifact(root, "report.json", ""); }, /size is outside/i], ["directory", async (root: string): Promise => { await mkdir(path.join(root, "report.json")); }, /not a regular file/i], ["oversized", async (root: string): Promise => { await writeArtifact(root, "report.json", "12345"); }, /size is outside/i], ["invalid UTF-8", async (root: string): Promise => { await writeArtifact(root, "report.json", Buffer.from([0xc3, 0x28])); }, /encoded data was not valid|UTF-8/i], ["primitive JSON", async (root: string): Promise => { await writeArtifact(root, "report.json", "1\n"); }, /object/i], ["array JSON", async (root: string): Promise => { await writeArtifact(root, "report.json", "[]\n"); }, /object/i], ]; it.each(invalidFixtures)("rejects %s artifacts", async (_name, setup, diagnostic) => { const root = await temporaryRoot("ci-artifact-invalid-"); await setup(root); await expect( validateCiArtifact({ root, artifact: artifact("report.json", "ci-contract"), schema: { id: "ci-contract", kind: "json", maxBytes: _name === "oversized" ? 4 : 4_096, executableSchemaId: "ci-contract-report", }, }), ).rejects.toThrow(diagnostic); }); it("rejects leaf and ancestor symlinks before opening evidence", async () => { const root = await temporaryRoot("ci-artifact-symlink-"); await writeArtifact(root, "real.json", "{\"ok\":true}\n"); await symlink("real.json", path.join(root, "leaf.json")); await mkdir(path.join(root, "real-directory")); await writeArtifact(root, "real-directory/report.json", "{\"ok\":true}\n"); await symlink("real-directory", path.join(root, "linked-directory")); const schema = { id: "ci-contract", kind: "json", maxBytes: 4_096, executableSchemaId: "ci-contract-report", } as const; await expect( validateCiArtifact({ root, artifact: artifact("leaf.json", "ci-contract"), schema }), ).rejects.toThrow(/not a regular file/i); await expect( validateCiArtifact({ root, artifact: artifact("linked-directory/report.json", "ci-contract"), schema, }), ).rejects.toThrow(/ancestor is unsafe/i); }); it("rejects strict JSON evidence with unknown fields", async () => { const root = await temporaryRoot("ci-artifact-strict-"); await writeArtifact( root, "report.json", `${JSON.stringify({ schemaVersion: 2, nodeVersion: "24.14.0", gateCount: 26, commandDefinitionCount: 81, commandReferenceCount: 93, artifactCount: 105, jobCount: 9, workflowSha256: "a".repeat(64), durationStatus: "UNSUPPORTED", negativeFixtures: [], failures: [], passed: true, unknown: true })}\n`, ); await expect( validateCiArtifact({ root, artifact: artifact("report.json", "ci-contract"), schema: { id: "ci-contract", kind: "json", maxBytes: 8_192, executableSchemaId: "ci-contract-report", }, }), ).rejects.toThrow(/unrecognized|unknown/i); }); it.each([ ["broken.xml", { id: "junit", kind: "junit", maxBytes: 1_024 }, ""], ["mismatched.xml", { id: "junit", kind: "junit", maxBytes: 1_024 }, ""], ["trailing.xml", { id: "junit", kind: "junit", maxBytes: 1_024 }, "garbage"], ["doctype.xml", { id: "junit", kind: "junit", maxBytes: 1_024 }, "]>"], ["entity.xml", { id: "junit", kind: "junit", maxBytes: 1_024 }, ""], ["numeric-entity.xml", { id: "junit", kind: "junit", maxBytes: 1_024 }, ""], ["duplicate-root.xml", { id: "junit", kind: "junit", maxBytes: 1_024 }, ""], ["broken.html", { id: "html", kind: "html", maxBytes: 1_024 }, ""], ["mismatched.html", { id: "html", kind: "html", maxBytes: 1_024 }, "
"], ["duplicate-root.html", { id: "html", kind: "html", maxBytes: 1_024 }, ""], ["invalid-root.html", { id: "html", kind: "html", maxBytes: 1_024 }, ""], ["entity.html", { id: "html", kind: "html", maxBytes: 1_024 }, "]>"], ["trailing.html", { id: "html", kind: "html", maxBytes: 1_024 }, "garbage"], ] as const)("rejects structurally incomplete %s", async (relative, schema, content) => { const root = await temporaryRoot("ci-artifact-structure-"); await writeArtifact(root, relative, content); await expect( validateCiArtifact({ root, artifact: artifact(relative, schema.id), schema: schema as CiGateArtifactSchema, }), ).rejects.toThrow(/invalid (?:JUnit|HTML) artifact/u); }); it.each([ ["report.xml", { id: "junit", kind: "junit", maxBytes: 4_096 }, ''], ["report.html", { id: "html", kind: "html", maxBytes: 4_096 }, ''], ["playwright.html", { id: "html", kind: "html", maxBytes: 4_096 }, ''], ] as const)("accepts bounded, well-formed structured %s evidence", async (relative, schema, content) => { const root = await temporaryRoot("ci-artifact-structured-valid-"); await writeArtifact(root, relative, content); await expect( validateCiArtifact({ root, artifact: artifact(relative, schema.id), schema: schema as CiGateArtifactSchema, }), ).resolves.toBeUndefined(); }); it.each([ ["deep.xml", "junit", `${"".repeat(256)}${"".repeat(256)}`], ["deep.html", "html", `${"
".repeat(256)}${"
".repeat(256)}`], ["many.xml", "junit", `${"".repeat(100_000)}`], ] as const)("rejects parser resource exhaustion in %s", async (relative, kind, content) => { const root = await temporaryRoot("ci-artifact-structured-bound-"); await writeArtifact(root, relative, content); await expect( validateCiArtifact({ root, artifact: artifact(relative, kind), schema: { id: kind, kind, maxBytes: Buffer.byteLength(content) } as CiGateArtifactSchema, }), ).rejects.toThrow(/invalid (?:JUnit|HTML) artifact/u); }); it("registers a semantic validator for every configured structured artifact", async () => { const contract = parseCiGateContract( JSON.parse(await readFile("config/ci/gates.json", "utf8")) as unknown, ); expect( contract.artifactSchemas.filter( (schema) => !hasCiArtifactSemanticValidator(schema), ), ).toEqual([]); expect( contract.artifactSchemas.some( (schema) => schema.kind === "json" && (schema.executableSchemaId as string) === "generic-json-object", ), ).toBe(false); }); it.each([ ["automated-a11y", { schemaVersion: 1, automatedStatus: "passed" }], ["manual-a11y", { schemaVersion: 1, coherentRelease: true, passed: true }], ["architecture-dependency-report", { staticImportGraph: {} }], ["design-system-contract", { schemaVersion: 1, mode: "source", checkedTokenCount: 1, failures: ["x"], passed: true }], ["i18n-contract", { schemaVersion: 1, mode: "source", localeCount: 1, messageKeyCount: 1, checkedFiles: 1, failures: ["x"], passed: true }], ["diagnostics-contract", { schemaVersion: 1, mode: "source", telemetryEventCount: 1, diagnosticEventCount: 1, checkedFiles: 1, failures: ["x"], passed: true }], ["realtime-boundaries", { schemaVersion: 1, sourceRoot: "src", violations: [{}], passed: true }], ["optional-recipes", { schemaVersion: 1, decisionId: "VD-10", violations: ["x"], passed: true }], ["optional-recipe-fixtures", { schemaVersion: 1, results: [], bundleBudgetFixtures: [], passed: true }], ["registry-compatibility-fixtures", { schemaVersion: 1, results: [{ id: "x", expected: false, actual: true, passed: true }] }], ["reproducible-build", { schemaVersion: 1, sourceDateEpoch: "1", buildId: "x", commitSha: "x", releaseId: "x", runnerImage: "x", status: "PASS", firstDigest: "BUILD_FAILED", secondDigest: "BUILD_FAILED", restored: true }], ["supply-chain-fixtures", { schemaVersion: 1, results: [{ id: "x", passed: true }, { id: "x", passed: true }] }], ["supply-chain-provider-fixtures", { schemaVersion: 1, fixtures: {}, passingFixtureCount: 1, status: "PASS" }], ["compatibility-fixtures", { schemaVersion: 1, generatedAt: "2026-08-02T00:00:00.000Z", rules: [], results: [] }], ["documentation-review", { schemaVersion: 1, passed: true, results: [] }], ["hosting-headers", { schemaVersion: 1, passed: true, results: [{ passed: false }] }], ] as const)("rejects pattern-matching but semantically invalid %s JSON", async (executableSchemaId, value) => { const root = await temporaryRoot("ci-artifact-semantic-"); await writeArtifact(root, "report.json", `${JSON.stringify(value)}\n`); await expect( validateCiArtifact({ root, artifact: artifact("report.json", executableSchemaId), schema: { id: executableSchemaId, kind: "json", maxBytes: 32_768, executableSchemaId, } as CiGateArtifactSchema, }), ).rejects.toThrow(); }); it("fails closed when a regular artifact grows after its bounded lstat", async () => { const root = await temporaryRoot("ci-artifact-growth-"); await writeArtifact(root, "report.txt", "1234"); const realHandle = await open(path.join(root, "report.txt"), "r"); await expect( readBoundedRegularFile( { root, relativePath: "report.txt", maxBytes: 4 }, { openFile: async () => ({ stat: async () => realHandle.stat(), read: async (buffer, offset) => { Buffer.from("12345").copy(buffer, offset); return { bytesRead: 5 }; }, close: async () => realHandle.close(), }), }, ), ).rejects.toThrow(/changed size or exceeds bound/i); }); it("rejects cross-field tampering in risk coverage evidence", async () => { const root = await temporaryRoot("ci-artifact-risk-"); const risk = { schemaVersion: 3, policy: "config/testing/risk-coverage.json", summary: "artifacts/tests/coverage/coverage-summary.json", status: "PASS", selectedTotal: 2, repositoryTotal: 2, counterBearingTotal: 1, instrumentedCounterBearingTotal: 1, counterlessTotal: 1, counterlessModules: ["src/types.ts"], preExclusionTotal: 2, generatedExclusionCount: 0, generatedExclusions: [], ownershipScope: "ALL_POLICY_HIGH_RISK", ownedHighRiskPaths: ["src/runtime.ts"], waivedHighRiskPaths: [], uncoveredModules: [], results: ["lines", "statements", "functions", "branches"].map((metric) => ({ scope: "summary", metric, threshold: 80, received: 90, passed: true, })), failures: [], }; const schema = { id: "risk", kind: "json", maxBytes: 16_384, executableSchemaId: "risk-coverage-v3", } as const; await writeArtifact(root, "risk.json", `${JSON.stringify(risk)}\n`); await expect( validateCiArtifact({ root, artifact: artifact("risk.json", "risk"), schema }), ).resolves.toBeUndefined(); await writeArtifact( root, "risk.json", `${JSON.stringify({ ...risk, counterlessTotal: 0 })}\n`, ); await expect( validateCiArtifact({ root, artifact: artifact("risk.json", "risk"), schema }), ).rejects.toThrow(/counter partition|counterless list length/u); for (const [mutation, diagnostic] of [ [{ preExclusionTotal: 3 }, /pre-exclusion inventory total drift/u], [{ status: "PASS", failures: [], results: risk.results.map((entry, index) => index === 0 ? { ...entry, received: 70, passed: false } : entry) }, /status must agree with failures and threshold results/u], [{ waivedHighRiskPaths: ["src/runtime.ts"] }, /owned and waived high-risk paths overlap/u], [{ results: risk.results.slice(0, 3) }, /all four metrics/u], [{ results: [...risk.results, risk.results[0]] }, /duplicated within scope/u], [{ results: [] }, /too small|at least 4/iu], ] as const) { await writeArtifact(root, "risk.json", `${JSON.stringify({ ...risk, ...mutation })}\n`); await expect( validateCiArtifact({ root, artifact: artifact("risk.json", "risk"), schema }), ).rejects.toThrow(diagnostic); } }); it("rejects coverage counters whose covered and skipped partitions exceed total", async () => { const root = await temporaryRoot("ci-artifact-coverage-"); const counter = { total: 10, covered: 8, skipped: 3, pct: 80 }; await writeArtifact( root, "coverage.json", `${JSON.stringify({ total: { lines: counter, statements: counter, functions: counter, branches: counter } })}\n`, ); await expect( validateCiArtifact({ root, artifact: artifact("coverage.json", "coverage"), schema: { id: "coverage", kind: "json", maxBytes: 4_096, executableSchemaId: "coverage-summary-v8", }, }), ).rejects.toThrow(/coverage counter exceeds total/u); }); }); describe("candidate archive and provider upload boundaries", () => { it("accepts only the manifest-bound candidate member set and bytes", async () => { const fixture = await createCandidateArchiveFixture(); await expect( verifyCiCandidateArchive({ archivePath: fixture.archivePath }), ).resolves.toEqual( expect.objectContaining({ archiveSha256: sha256(await readFile(fixture.archivePath)) }), ); }); it("rejects an extra candidate member before extraction", async () => { const fixture = await createCandidateArchiveFixture({ extraMember: true }); await expect( verifyCiCandidateArchive({ archivePath: fixture.archivePath }), ).rejects.toThrow(/exact member set drift before extraction/i); }); it("rejects duplicate archive members before extraction", async () => { const fixture = await createCandidateArchiveFixture({ duplicateMember: true }); await expect(verifyCiCandidateArchive({ archivePath: fixture.archivePath })) .rejects.toThrow(/duplicate member/i); }); it.each(["symlink", "hardlink"] as const)("rejects a %s archive member without touching an outside canary", async (kind) => { const root = await temporaryRoot(`ci-candidate-${kind}-`); const outside = await temporaryRoot(`ci-candidate-${kind}-outside-`); const canary = path.join(outside, "canary"); await writeFile(canary, "unchanged\n"); await writeArtifact(root, "target", "target\n"); if (kind === "symlink") await symlink("target", path.join(root, "unsafe")); else await link(path.join(root, "target"), path.join(root, "unsafe")); const archivePath = path.join(root, "unsafe.tar.gz"); const tar = spawnSync("/usr/bin/tar", ["-czf", archivePath, ...(kind === "hardlink" ? ["target"] : []), "unsafe"], { cwd: root, encoding: "utf8" }); if (tar.status !== 0) throw new Error(tar.stderr); await expect(verifyCiCandidateArchive({ archivePath })).rejects.toThrow(/non-regular member/i); await expect(readFile(canary, "utf8")).resolves.toBe("unchanged\n"); }); it("rejects traversal members and preserves the outside canary", async () => { const root = await temporaryRoot("ci-candidate-traversal-"); const canary = path.join(root, "outside-canary"); await writeArtifact(root, "safe", "safe\n"); await writeFile(canary, "unchanged\n"); const archivePath = path.join(root, "traversal.tar.gz"); const tar = spawnSync("/usr/bin/tar", ["-czf", archivePath, "--transform=s|safe|../outside-canary|", "safe"], { cwd: root, encoding: "utf8" }); if (tar.status !== 0) throw new Error(tar.stderr); await expect(verifyCiCandidateArchive({ archivePath })).rejects.toThrow(/unsafe member path/i); await expect(readFile(canary, "utf8")).resolves.toBe("unchanged\n"); }); it("rejects an oversized manifest from tar headers before full extraction", async () => { const fixture = await createCandidateArchiveFixture({ oversizedManifest: true }); await expect(verifyCiCandidateArchive({ archivePath: fixture.archivePath })) .rejects.toThrow(/manifest exceeds 8388608 bytes/i); }); it("rejects an expanded-byte bomb before extraction and preserves its canary", async () => { const root = await temporaryRoot("ci-candidate-expanded-bomb-"); const huge = path.join(root, "huge.bin"); const handle = await open(huge, "w"); await handle.truncate(268_435_457); await handle.close(); const canary = path.join(root, "canary"); await writeFile(canary, "unchanged\n"); const archivePath = path.join(root, "bomb.tar.gz"); const tar = spawnSync("/usr/bin/tar", ["-czf", archivePath, "huge.bin"], { cwd: root, encoding: "utf8", timeout: 30_000, }); if (tar.status !== 0) throw new Error(tar.stderr || String(tar.error)); await expect(verifyCiCandidateArchive({ archivePath })) .rejects.toThrow(/expanded bytes exceed the bound/i); await expect(readFile(canary, "utf8")).resolves.toBe("unchanged\n"); }, 40_000); it.each([ ["missing value", ["--archive"]], [ "option-like value", ["--archive", "missing.tar.gz", "--extract-to", "--github-output", "out"], ], ])("maps a %s to the deterministic CLI Usage result", (_label, arguments_) => { expect(parseCandidateArchiveArguments(arguments_)).toBeNull(); expect(CANDIDATE_ARCHIVE_USAGE).toBe( "Usage: verify-ci-candidate-archive --archive [--extract-to ] [--github-output ]\n", ); }); it("rejects archive digest mismatch and symlink substitution", async () => { const fixture = await createCandidateArchiveFixture(); await expect( verifyCiCandidateArchive({ archivePath: fixture.archivePath, expectedSha256: "0".repeat(64), }), ).rejects.toThrow(/SHA-256 mismatch/u); const linked = `${fixture.archivePath}.link`; await symlink(path.basename(fixture.archivePath), linked); await expect( verifyCiCandidateArchive({ archivePath: linked }), ).rejects.toThrow(/regular non-symlink/u); }); it("rejects an excessive archive member universe before per-member reads", async () => { const fixture = await createCandidateArchiveFixture({ repeatedExtraMembers: 8_200 }); await expect( verifyCiCandidateArchive({ archivePath: fixture.archivePath }), ).rejects.toThrow(/member count is outside 1\.\.8192|exceeds 8192 members/u); }); it("uses the captured archive inode when the pathname is replaced mid-verification", async () => { const original = await createCandidateArchiveFixture(); const replacement = await createCandidateArchiveFixture({ extraMember: true }); const originalArchiveSha256 = sha256(await readFile(original.archivePath)); const displaced = `${original.archivePath}.displaced`; const extractTo = path.join(path.dirname(original.archivePath), "verified-candidate"); await expect( verifyCiCandidateArchive( { archivePath: original.archivePath, extractTo, repositoryRoot: path.dirname(original.archivePath), }, { afterArchiveRead: async () => { await rename(original.archivePath, displaced); await rename(replacement.archivePath, original.archivePath); }, }, ), ).resolves.toEqual(expect.objectContaining({ archiveSha256: originalArchiveSha256 })); await expect(readFile(displaced)).resolves.toBeDefined(); await expect(readFile(path.join(extractTo, "dist/app.js"), "utf8")).resolves.toBe("app\n"); }); it("validates provider JSON against candidate dist and lockfile digests", async () => { const fixture = await createProviderFixture(); await expect( validateProviderUpload(providerValidationInput(fixture)), ).resolves.toEqual(expect.objectContaining({ provider: "fixture" })); const report = JSON.parse(await readFile(fixture.reportPath, "utf8")) as Record; report.candidate.distSha256 = "f".repeat(64); report.signature.value = sign( null, providerEvidenceSignaturePayload(report), fixture.privateKey, ).toString("base64"); await expect( validateProviderUpload( providerValidationInput(fixture, Buffer.from(`${JSON.stringify(report)}\n`)), ), ).rejects.toThrow(/candidate identity mismatch/i); }, 30_000); it("uses the reverified archive manifest when extracted candidate files are mutated", async () => { const fixture = await createProviderFixture(); await writeArtifact(fixture.candidateRoot, "dist/app.js", "mutated\n"); const mutableManifest = JSON.parse( await import("node:fs/promises").then(({ readFile }) => readFile(path.join(fixture.candidateRoot, RELEASE_CANDIDATE_MANIFEST_PATH), "utf8"), ), ) as Record; mutableManifest.distSha256 = "e".repeat(64); await writeFile( path.join(fixture.candidateRoot, RELEASE_CANDIDATE_MANIFEST_PATH), `${JSON.stringify(mutableManifest)}\n`, ); await expect( validateProviderUpload(providerValidationInput(fixture)), ).rejects.toThrow(/candidate root changed/i); }); it("rejects symlinked provider reports at the bounded file boundary", async () => { const fixture = await createProviderFixture(); const real = path.join(fixture.root, "real-report.json"); await writeFile(real, await readFile(fixture.reportPath)); await rm(fixture.reportPath); await symlink(path.relative(path.dirname(fixture.reportPath), real), fixture.reportPath); await expect( readBoundedRegularFile({ root: fixture.root, relativePath: path.relative(fixture.root, fixture.reportPath), maxBytes: 8_388_608, }), ).rejects.toThrow(/not a regular file/i); }); it("rejects oversized provider reports before JSON parsing", async () => { const fixture = await createProviderFixture(); await writeFile(fixture.reportPath, Buffer.alloc(8_388_609, 0x20)); await expect( readBoundedRegularFile({ root: fixture.root, relativePath: path.relative(fixture.root, fixture.reportPath), maxBytes: 8_388_608, }), ).rejects.toThrow(/size is outside/u); }); it("rejects a stale raw provider report before starting the provider", async () => { const fixture = await createProviderFixture(); const markerPath = path.join(fixture.root, "provider-started"); const result = runProviderSupervisor(fixture, { command: `node -e 'require("node:fs").writeFileSync(${JSON.stringify(markerPath)}, "started")'`, sealedPath: path.join(fixture.root, "provider-evidence/vulnerability-report.json"), }); expect(result.status).not.toBe(0); expect(result.stderr).toMatch(/raw provider report already exists/i); await expect(readFile(markerPath)).rejects.toMatchObject({ code: "ENOENT" }); }); it.each(["invalid trust", "invalid archive", "provider failure"] as const)( "leaves no stale raw report after %s and permits a clean retry", async (failure) => { const fixture = await createProviderFixture(); const archiveBytes = await readFile(fixture.archivePath); const publicKeyBytes = await readFile(fixture.publicKeyPath); await writeFile(fixture.providerWriter, providerV2WriterSource()); await rm(fixture.reportPath); if (failure === "invalid trust") await writeFile(fixture.publicKeyPath, "not a public key\n"); if (failure === "invalid archive") await writeFile(fixture.archivePath, "not the captured archive\n"); const sealedPath = path.join(fixture.root, "provider-evidence/vulnerability-report.json"); const failed = runProviderSupervisor(fixture, { command: failure === "provider failure" ? "exit 9" : `node ${JSON.stringify(fixture.providerWriter)}`, sealedPath, }); expect(failed.status).not.toBe(0); await expect(lstat(fixture.reportPath)).rejects.toMatchObject({ code: "ENOENT" }); await writeFile(fixture.archivePath, archiveBytes); await writeFile(fixture.publicKeyPath, publicKeyBytes); const retried = runProviderSupervisor(fixture, { command: `node ${JSON.stringify(fixture.providerWriter)}`, sealedPath, }); expect(retried.status, retried.stderr).toBe(0); }, 20_000, ); it("drains and kills provider background processes before sealing evidence", async () => { const fixture = await createProviderFixture(); await rm(fixture.reportPath); const sealedPath = path.join( fixture.root, "provider-evidence/vulnerability-report.json", ); const mutatorMarker = path.join(fixture.root, "background-mutator-ran"); const providerScript = path.join(fixture.root, "provider.mjs"); const mutator = [ "process.on('SIGTERM', () => {});", "setTimeout(() => {", ` require('node:fs').writeFileSync(${JSON.stringify(fixture.protectedCandidatePath)}, 'mutated\\n');`, ` require('node:fs').writeFileSync(${JSON.stringify(sealedPath)}, '{"mutated":true}\\n');`, ` require('node:fs').writeFileSync(${JSON.stringify(mutatorMarker)}, 'ran\\n');`, "}, 1200);", ].join("\n"); await writeFile( providerScript, [ "import { spawn } from 'node:child_process';", providerV2WriterSource(), `const child = spawn(process.execPath, ['-e', ${JSON.stringify(mutator)}], { stdio: 'ignore' });`, "child.unref();", ].join("\n"), ); const result = runProviderSupervisor(fixture, { command: `node ${JSON.stringify(providerScript)}`, sealedPath, }); expect(result.status, result.stderr).toBe(0); await delay(1_500); await expect(readFile(fixture.protectedCandidatePath)).resolves.toEqual( fixture.protectedCandidateBytes, ); await expect(readFile(mutatorMarker)).rejects.toMatchObject({ code: "ENOENT" }); expect(JSON.parse(await readFile(sealedPath, "utf8"))).toEqual( expect.objectContaining({ provider: "fixture" }), ); }, 10_000); it("does not expose or mutate a host path outside the sandboxed workspace", async () => { const fixture = await createProviderFixture(); await rm(fixture.reportPath); const outside = await temporaryRoot("provider-host-canary-"); const canary = path.join(outside, "secret-canary"); await writeFile(canary, "host-secret\n"); const providerScript = path.join(fixture.root, "provider-host-boundary.mjs"); await writeFile(providerScript, [ "import { readFileSync, writeFileSync } from 'node:fs';", `try { readFileSync(${JSON.stringify(canary)}); process.exit(9); } catch {}`, `try { writeFileSync(${JSON.stringify(canary)}, 'mutated\\n'); } catch {}`, providerV2WriterSource({ importFs: false }), ].join("\n")); const result = runProviderSupervisor(fixture, { command: `node ${JSON.stringify(providerScript)}`, sealedPath: path.join(fixture.root, "provider-evidence/vulnerability-report.json"), }); expect(result.status, result.stderr).toBe(0); await expect(readFile(canary, "utf8")).resolves.toBe("host-secret\n"); }, 10_000); it("applies effective aggregate cgroup limits without exposing command or credentials", async () => { const fixture = await createProviderFixture(); await rm(fixture.reportPath); const credential = "live-provider-credential-sentinel"; const providerScript = path.join(fixture.root, "provider-live-cgroup.mjs"); await writeFile(providerScript, [ `if (process.env.VULNERABILITY_PROVIDER_SECRET !== ${JSON.stringify(credential)}) process.exit(9);`, `process.stdout.write(${JSON.stringify(credential)});`, `process.stderr.write(${JSON.stringify(credential)});`, providerV2WriterSource(), "setTimeout(() => process.exit(0), 1500);", ].join("\n")); const command = `node ${JSON.stringify(providerScript)}`; const execution = startProviderSupervisor(fixture, { command, sealedPath: path.join(fixture.root, "provider-evidence/vulnerability-report.json"), environment: { PROVIDER_SUPERVISOR_TIMEOUT_MS: "4000", VULNERABILITY_PROVIDER_SECRET: credential, }, }); const completionStarted = Date.now(); // Start recording before anything is asserted: the provider's whole life is // shorter than one `systemctl show`, so the tree has to be sampled, not // sampled once at whatever moment the assertions happen to arrive. const tree = recordProviderProcessTree(execution.child.pid!); const unit = await waitForProviderUnit("vulnerability", execution.child.pid); const properties = showProviderUnit(unit); expect(properties).toMatchObject({ ActiveState: "active", CPUQuotaPerSecUSec: "1s", CPUQuotaPeriodUSec: "100ms", KillMode: "control-group", MemoryMax: "1073741824", MemorySwapMax: "0", SendSIGKILL: "yes", TasksMax: "64", }); const cgroupRoot = path.resolve("/sys/fs/cgroup", `.${properties.ControlGroup}`); await expect(readFile(path.join(cgroupRoot, "memory.max"), "utf8")).resolves.toBe("1073741824\n"); await expect(readFile(path.join(cgroupRoot, "memory.swap.max"), "utf8")).resolves.toBe("0\n"); await expect(readFile(path.join(cgroupRoot, "pids.max"), "utf8")).resolves.toBe("64\n"); await expect(readFile(path.join(cgroupRoot, "cpu.max"), "utf8")).resolves.toBe("100000 100000\n"); expect(readProviderUnitMetadata(unit)).not.toMatch(new RegExp(`${command}|${credential}`, "u")); expect(showProcessArguments(execution.child.pid)).not.toMatch(new RegExp(`${command}|${credential}`, "u")); const reportIdentity = await lstat(fixture.reportPath); const result = await execution.completion; tree.stop(); expect(result.code, result.stderr).toBe(0); expect(result.stdout).not.toContain(credential); expect(result.stderr).not.toContain(credential); // Everything below reads the recording of the whole run rather than a live // snapshot. The sandbox exists for a few hundred milliseconds; asserting // while it runs meant racing it, and the assertions are about what the run // contained, not about what a particular instant looked like. const cgroupPids = tree.cgroupPids(); const processArguments = tree.cgroupArguments(); const directChildPids = tree.directChildPids(); const directChildArguments = tree.directChildArguments(); const observedArguments = [ showProcessArguments(execution.child.pid), ...directChildArguments, ...processArguments, ]; expect(observedArguments.join("\n")).not.toContain(credential); expect(directChildArguments.filter((arguments_) => arguments_.includes("/usr/bin/systemd-run"))).toHaveLength(1); expect(directChildArguments.filter((arguments_) => arguments_.includes("provider-raw-guardian.ts"))).toHaveLength(1); expect(processArguments.filter((arguments_) => arguments_.includes("provider-scope-wrapper.ts"))).toHaveLength(1); expect( processArguments.filter((arguments_) => /\/usr\/bin\/(?:bwrap|prlimit)/u.test(arguments_)).length, `sandbox never observed in the scope; recorded: ${processArguments.join(" | ")}`, ).toBeGreaterThan(0); const infrastructureArguments = [...directChildArguments, ...processArguments].filter((arguments_) => /provider-(?:scope-wrapper|raw-guardian)|systemd-run|bwrap|prlimit/u.test(arguments_), ); expect(infrastructureArguments.join("\n")).not.toContain(command); expect(processArguments.filter((arguments_) => arguments_.includes(providerScript))).toHaveLength(1); expect(Date.now() - completionStarted).toBeLessThan(4_000); await expectProviderUnitGone(unit); await expect(lstat(cgroupRoot)).rejects.toMatchObject({ code: "ENOENT" }); expect(cgroupPids.every((pid) => !processExists(pid))).toBe(true); expect(directChildPids.every((pid) => !processExists(pid))).toBe(true); await expect(lstat(fixture.reportPath)).rejects.toMatchObject({ code: "ENOENT" }); await expect(findProviderRawReferences(fixture.reportPath, reportIdentity)).resolves.toEqual([]); }, PROCESS_HEAVY_TIMEOUT_MS); it("kills and collects an active provider when its guardian dies", async () => { const fixture = await createProviderFixture(); await rm(fixture.reportPath); const sealedPath = path.join(fixture.root, "provider-evidence/vulnerability-report.json"); const providerScript = path.join(fixture.root, "provider-active-guardian-death.mjs"); await writeFile(providerScript, [ "import { writeFileSync } from 'node:fs';", `writeFileSync(${JSON.stringify(fixture.reportPath)}, 'started\\n');`, "setInterval(() => {}, 1000);", ].join("\n")); const execution = startProviderSupervisor(fixture, { command: `node ${JSON.stringify(providerScript)}`, sealedPath, environment: { PROVIDER_SUPERVISOR_TIMEOUT_MS: "5000" }, }); const unit = await waitForProviderUnit("vulnerability", execution.child.pid); await waitForFileContent(fixture.reportPath, "started\n"); const guardianPid = await waitForDirectChildMatching( execution.child.pid!, "provider-raw-guardian.ts", ); process.kill(guardianPid, "SIGKILL"); const result = await execution.completion; expect(result.code).not.toBe(0); expect(result.stderr).toMatch(/provider raw guardian failed|provider guardian failed|SIGKILL/iu); await expectProviderUnitGone(unit); await waitForProcessGone(guardianPid); await expect(lstat(fixture.reportPath)).rejects.toMatchObject({ code: "ENOENT" }); await expect(lstat(sealedPath)).rejects.toMatchObject({ code: "ENOENT" }); expect((await readdir(path.dirname(sealedPath))).filter((leaf) => leaf.includes(".guardian-"))) .toEqual([]); // `providerWriter` is only a path; the retry has to materialise the script // it names or the clean-retry claim is proven by a module-not-found error. await writeFile(fixture.providerWriter, providerV2WriterSource()); const retried = runProviderSupervisor(fixture, { command: `node ${JSON.stringify(fixture.providerWriter)}`, sealedPath, }); expect(retried.status, retried.stderr).toBe(0); }, 20_000); it("collects the whole provider scope when its supervisor dies", async () => { const fixture = await createProviderFixture(); await rm(fixture.reportPath); const providerScript = path.join(fixture.root, "provider-parent-death.mjs"); const detachedMarker = path.join(fixture.root, "detached-marker"); const detachedDescendant = [ "const {appendFileSync,writeFileSync}=require('node:fs');", `setTimeout(()=>appendFileSync(${JSON.stringify(fixture.reportPath)},'detached-survived\\n'),800);`, `setTimeout(()=>writeFileSync(${JSON.stringify(detachedMarker)},'survived\\n'),800);`, "setInterval(()=>{},1000);", ].join("\n"); await writeFile(providerScript, [ "import { spawn } from 'node:child_process';", "import { appendFileSync, writeFileSync } from 'node:fs';", `writeFileSync(${JSON.stringify(fixture.reportPath)}, 'started\\n');`, `const child=spawn(process.execPath,['-e',${JSON.stringify(detachedDescendant)}],{detached:true,stdio:'ignore'}); child.unref();`, `setTimeout(() => appendFileSync(${JSON.stringify(fixture.reportPath)}, 'survived\\n'), 800);`, "setInterval(() => {}, 1000);", ].join("\n")); const execution = startProviderSupervisor(fixture, { command: `node ${JSON.stringify(providerScript)}`, sealedPath: path.join(fixture.root, "provider-evidence/vulnerability-report.json"), }); const unit = await waitForProviderUnit("vulnerability", execution.child.pid); await waitForFileContent(fixture.reportPath, "started\n"); const reportIdentity = await lstat(fixture.reportPath); const cgroupRoot = path.resolve("/sys/fs/cgroup", `.${showProviderUnit(unit).ControlGroup}`); const cgroupPids = await readCgroupPids(cgroupRoot); expect(execution.child.kill("SIGKILL")).toBe(true); const result = await execution.completion; expect(result.signal).toBe("SIGKILL"); await expectProviderUnitGone(unit); await delay(900); await expect(lstat(fixture.reportPath)).rejects.toMatchObject({ code: "ENOENT" }); await expect(lstat(detachedMarker)).rejects.toMatchObject({ code: "ENOENT" }); await expect(lstat(cgroupRoot)).rejects.toMatchObject({ code: "ENOENT" }); expect(cgroupPids.every((pid) => !processExists(pid))).toBe(true); await expect(findProviderRawReferences(fixture.reportPath, reportIdentity)).resolves.toEqual([]); }, 10_000); it("fails closed when the guardian dies after scope collection and before commit", async () => { const fixture = await createProviderFixture(); await rm(fixture.reportPath); await writeFile(fixture.providerWriter, providerV2WriterSource()); const sealedPath = path.join(fixture.root, "provider-evidence/vulnerability-report.json"); const outputPipe = path.join(fixture.root, "blocked-github-output"); const fifo = spawnSync("mkfifo", [outputPipe], { encoding: "utf8" }); if (fifo.error) throw fifo.error; if (fifo.status !== 0) throw new Error(`mkfifo failed: ${fifo.stderr}`); const execution = startProviderSupervisor(fixture, { command: `node ${JSON.stringify(fixture.providerWriter)}`, sealedPath, environment: { GITHUB_OUTPUT: outputPipe }, }); let completed = false; let outputReader: Awaited> | undefined; try { const unit = await waitForProviderUnit("vulnerability", execution.child.pid); await expectProviderUnitGone(unit); await waitForExistingPath(sealedPath); const guardianPid = await waitForDirectChildMatching( execution.child.pid!, "provider-raw-guardian.ts", ); expect(showProcessArguments(guardianPid)).not.toContain(fixture.reportPath); process.kill(guardianPid, "SIGKILL"); outputReader = await open(outputPipe, constants.O_RDONLY | constants.O_NONBLOCK); const result = await execution.completion; completed = true; expect(result.code).not.toBe(0); expect(result.stderr).toMatch(/provider guardian failed|SIGKILL/iu); await waitForProcessGone(guardianPid); await waitForMissingPath(fixture.reportPath); await waitForMissingPath(sealedPath); expect((await readdir(path.dirname(sealedPath))).filter((leaf) => leaf.includes(".guardian-"))) .toEqual([]); } finally { await outputReader?.close(); if (!completed && execution.child.exitCode === null && execution.child.signalCode === null) { execution.child.kill("SIGKILL"); await execution.completion; } } }, 10_000); it("cleans published evidence when the supervisor dies and permits same-workspace retry", async () => { const fixture = await createProviderFixture(); await rm(fixture.reportPath); await writeFile(fixture.providerWriter, providerV2WriterSource()); const sealedPath = path.join(fixture.root, "provider-evidence/vulnerability-report.json"); const outputPipe = path.join(fixture.root, "supervisor-death-github-output"); const fifo = spawnSync("mkfifo", [outputPipe], { encoding: "utf8" }); if (fifo.error) throw fifo.error; if (fifo.status !== 0) throw new Error(`mkfifo failed: ${fifo.stderr}`); const execution = startProviderSupervisor(fixture, { command: `node ${JSON.stringify(fixture.providerWriter)}`, sealedPath, environment: { GITHUB_OUTPUT: outputPipe }, }); let completed = false; try { const unit = await waitForProviderUnit("vulnerability", execution.child.pid); await expectProviderUnitGone(unit); await waitForExistingPath(sealedPath); const guardianPid = await waitForDirectChildMatching( execution.child.pid!, "provider-raw-guardian.ts", ); expect(execution.child.kill("SIGKILL")).toBe(true); const result = await execution.completion; completed = true; expect(result.signal).toBe("SIGKILL"); await waitForProcessGone(guardianPid); await waitForMissingPath(fixture.reportPath); await waitForMissingPath(sealedPath); expect((await readdir(path.dirname(sealedPath))).filter((leaf) => leaf.includes(".guardian-"))) .toEqual([]); const retried = runProviderSupervisor(fixture, { command: `node ${JSON.stringify(fixture.providerWriter)}`, sealedPath, }); expect(retried.status, retried.stderr).toBe(0); } finally { if (!completed && execution.child.exitCode === null && execution.child.signalCode === null) { execution.child.kill("SIGKILL"); await execution.completion; } } }, 15_000); it("observes EMFILE at the provider FD limit and still emits valid evidence", async () => { const fixture = await createProviderFixture(); await rm(fixture.reportPath); const adapter = path.join(fixture.root, "provider-fd-limit.mjs"); await writeFile(adapter, [ "import { closeSync, openSync } from 'node:fs';", "const descriptors = [];", "let observed = false;", "for (let index=0; index<128; index+=1) { try { descriptors.push(openSync('/dev/null','r')); } catch (error) { if (error?.code === 'EMFILE') observed=true; break; } }", "if (!observed) process.exit(9);", "for (const descriptor of descriptors) closeSync(descriptor);", providerV2WriterSource(), ].join("\n")); const result = runProviderSupervisor(fixture, { command: `node ${JSON.stringify(adapter)}`, sealedPath: path.join(fixture.root, "provider-evidence/vulnerability-report.json"), }); expect(result.status, result.stderr).toBe(0); await expect(lstat(fixture.reportPath)).rejects.toMatchObject({ code: "ENOENT" }); }, 10_000); it("enforces CPU RLIMIT before the independent wall-clock timeout", async () => { const fixture = await createProviderFixture(); await rm(fixture.reportPath); const started = Date.now(); const result = runProviderSupervisor(fixture, { command: "node -e 'while(true){}'", sealedPath: path.join(fixture.root, "provider-evidence/vulnerability-report.json"), environment: { PROVIDER_SUPERVISOR_CPU_SECONDS: "1", PROVIDER_SUPERVISOR_TIMEOUT_MS: "5000" }, spawnTimeoutMs: 7_000, }); expect(Date.now() - started).toBeLessThan(5_000); expect(result.status).not.toBe(0); expect(result.stderr).toMatch(/exit=(?:137|152)|signal=SIG(?:XCPU|KILL)/iu); expect(result.stderr).not.toMatch(/timed out/iu); await expect(lstat(fixture.reportPath)).rejects.toMatchObject({ code: "ENOENT" }); }, 10_000); it.each(["output", "timeout"] as const)( "kills descendants and collects the scope on provider %s termination", async (reason) => { const fixture = await createProviderFixture(); await rm(fixture.reportPath); const providerScript = path.join(fixture.root, `provider-${reason}-descendant.mjs`); const descendant = [ "const {appendFileSync}=require('node:fs');", `setTimeout(()=>appendFileSync(${JSON.stringify(fixture.reportPath)},'survived\\n'),1600);`, "setInterval(()=>{},1000);", ].join("\n"); await writeFile(providerScript, [ "import { spawn } from 'node:child_process';", "import { writeFileSync } from 'node:fs';", `writeFileSync(${JSON.stringify(fixture.reportPath)}, 'started\\n');`, `const child=spawn(process.execPath,['-e',${JSON.stringify(descendant)}],{detached:true,stdio:'ignore'}); child.unref();`, ...(reason === "output" ? ["setTimeout(()=>{process.stdout.write('o'.repeat(700000));process.stderr.write('e'.repeat(700000));},400);"] : []), "setInterval(()=>{},1000);", ].join("\n")); const execution = startProviderSupervisor(fixture, { command: `node ${JSON.stringify(providerScript)}`, sealedPath: path.join(fixture.root, "provider-evidence/vulnerability-report.json"), environment: { PROVIDER_SUPERVISOR_TIMEOUT_MS: reason === "timeout" ? "500" : "5000" }, }); const unit = await waitForProviderUnit("vulnerability", execution.child.pid); await waitForFileContent(fixture.reportPath, "started\n"); const reportIdentity = await lstat(fixture.reportPath); const cgroupRoot = path.resolve("/sys/fs/cgroup", `.${showProviderUnit(unit).ControlGroup}`); const cgroupPids = await readCgroupPids(cgroupRoot); const result = await execution.completion; expect(result.code).not.toBe(0); expect(result.stderr).toMatch(reason === "timeout" ? /timed out/iu : /output exceeded/iu); expect(Buffer.byteLength(`${result.stdout}${result.stderr}`)).toBeLessThan(1_200_000); await expectProviderUnitGone(unit); await delay(1_300); await expect(lstat(fixture.reportPath)).rejects.toMatchObject({ code: "ENOENT" }); await expect(lstat(cgroupRoot)).rejects.toMatchObject({ code: "ENOENT" }); expect(cgroupPids.every((pid) => !processExists(pid))).toBe(true); await expect(findProviderRawReferences(fixture.reportPath, reportIdentity)).resolves.toEqual([]); }, 10_000, ); it.each(["vulnerability", "provenance"] as const)( "runs the %s adapter offline and collects its unit", async (kind) => { const fixture = await createProviderFixture(); const canary = await startLoopbackCanary(fixture.root); try { const reportPath = kind === "vulnerability" ? fixture.reportPath : path.join(fixture.root, "provider-evidence/untrusted/provenance-attestation.json"); await rm(reportPath, { force: true }); const adapter = path.join(fixture.root, `offline-${kind}-adapter.mjs`); await writeFile(adapter, [ "import { readFileSync, writeFileSync } from 'node:fs';", "import { createConnection } from 'node:net';", `if (!/0::.*\\/ca-provider-${kind}-[1-9][0-9]*-[0-9a-f]{24}\\.scope(?:\\n|$)/u.test(readFileSync('/proc/self/cgroup','utf8'))) process.exit(9);`, `await new Promise((resolve) => { const socket=createConnection({host:'127.0.0.1',port:Number(process.env.${kind.toUpperCase()}_PROVIDER_LOOPBACK_PORT)}); socket.once('connect',()=>process.exit(9)); socket.once('error',()=>resolve()); });`, providerV2WriterSource({ kind, importFs: false }), ].join("\n")); const execution = startProviderSupervisor(fixture, { kind, command: `node ${JSON.stringify(adapter)}`, reportPath, sealedPath: path.join( fixture.root, `provider-evidence/${kind === "vulnerability" ? "vulnerability-report.json" : "provenance-attestation.json"}`, ), environment: { [`${kind.toUpperCase()}_PROVIDER_LOOPBACK_PORT`]: String(canary.port) }, }); const unit = await waitForProviderUnit(kind, execution.child.pid); const result = await execution.completion; expect(result.code, result.stderr).toBe(0); await expectProviderUnitGone(unit); await expect(lstat(canary.marker)).rejects.toMatchObject({ code: "ENOENT" }); } finally { canary.child.kill("SIGKILL"); await waitForChildClose(canary.child); } }, 10_000, ); }); describe("verified promotion finalizer", () => { it("enforces exact private modes even under a fully restrictive umask", async () => { const fixture = await createPromotionStagingFixture(); let previous: number | undefined; let finalized: Awaited>; try { finalized = await stageVerifiedPromotion(fixture.input, { ...fixture.dependencies, beforePublish: async () => { previous = process.umask(0o777); }, }); } finally { if (previous !== undefined) process.umask(previous); } expect((await lstat(finalized.stagingRoot)).mode & 0o777).toBe(0o700); for (const file of finalized.files) { expect((await lstat(path.join(finalized.stagingRoot, file.name))).mode & 0o777).toBe(0o400); } await cleanupFinalizedPromotion(finalizedCleanup(fixture, finalized)); }, 30_000); it("creates no input records and publishes deterministic exact-five strict v3 bindings", async () => { const fixture = await createPromotionStagingFixture(); let archiveCaptureCount = 0; await expect(readFile(path.join(fixture.root, "artifacts/security/provider-verification.json"))) .rejects.toMatchObject({ code: "ENOENT" }); await expect(readFile(path.join(fixture.root, "artifacts/security/promotion-verification.json"))) .rejects.toMatchObject({ code: "ENOENT" }); const dependencies = { ...fixture.dependencies, captureArchive: async (input: Parameters[0]) => { archiveCaptureCount += 1; return captureCiCandidateArchive(input); }, }; const finalized = await stageVerifiedPromotion(fixture.input, dependencies); expect(archiveCaptureCount).toBe(1); expect(finalized.files.map(({ name }) => name)).toEqual(PROMOTED_FILE_NAMES); expect((await readdir(finalized.stagingRoot)).sort()).toEqual([...PROMOTED_FILE_NAMES].sort()); expect(finalized.stagingRoot).toBe(path.join(fixture.runnerTempRoot, fixture.cleanupToken)); expect(finalized.stagingRoot).not.toContain(".release/promoted-staging"); expect((await lstat(finalized.stagingRoot)).mode & 0o777).toBe(0o700); for (const file of finalized.files) { expect((await lstat(path.join(finalized.stagingRoot, file.name))).mode & 0o777).toBe(0o400); expect(file.sha256).toBe(sha256(await readFile(path.join(finalized.stagingRoot, file.name)))); } const providerBytes = await readFile(path.join(finalized.stagingRoot, "provider-verification.json")); const promotionBytes = await readFile(path.join(finalized.stagingRoot, "promotion-verification.json")); const provider = providerVerificationArtifactSchema.parse(JSON.parse(providerBytes.toString("utf8"))); const promotion = providerVerificationArtifactSchema.parse(JSON.parse(promotionBytes.toString("utf8"))); expect(provider.artifactType).toBe("provider-verification"); expect(promotion.artifactType).toBe("promotion-verification"); if (provider.artifactType !== "provider-verification" || promotion.artifactType !== "promotion-verification") { throw new Error("verification record role narrowing failed"); } expect(provider.status).toBe("PASS"); expect(promotion.status).toBe("PASS"); expect(promotion.providerVerificationSha256).toBe(sha256(providerBytes)); expect(promotion.localEvidenceAssessmentSha256).toBe(fixture.assessmentSha256); expect(promotion.run).toEqual(fixture.expectedContext.run); expect(promotion.source).toEqual(fixture.expectedContext.source); expect(promotion.candidate).toEqual(fixture.expectedContext.candidate); expect(promotion.providerEvidence).toEqual(provider.providerEvidence); expect(promotion.providerEvidence).toEqual(fixture.expectedProviderEvidence); expect(promotion.trustPolicySha256).toBe(provider.trustPolicySha256); expect(provider.vulnerabilityStatus).toBe("PASS"); expect(provider.provenanceAttestationStatus).toBe("PASS"); const exactFive = Object.fromEntries( await Promise.all( PROMOTED_FILE_NAMES.map(async (name) => [ name, await readFile(path.join(finalized.stagingRoot, name)), ] as const), ), ); await expect( verifyExactPromotionBundle(exactFive, fixture.bundleVerification), ).resolves.toMatchObject({ status: "PASS" }); await cleanupFinalizedPromotion(finalizedCleanup(fixture, finalized)); const repeated = await stageVerifiedPromotion(fixture.input, dependencies); expect(archiveCaptureCount).toBe(2); await expect(readFile(path.join(repeated.stagingRoot, "provider-verification.json"))) .resolves.toEqual(providerBytes); await expect(readFile(path.join(repeated.stagingRoot, "promotion-verification.json"))) .resolves.toEqual(promotionBytes); await cleanupFinalizedPromotion(finalizedCleanup(fixture, repeated)); }, 30_000); it("passes the downstream CLI for a real finalized exact-five with external expected identity", async () => { const fixture = await createPromotionStagingFixture({ nowEpochMs: Date.now() }); const finalized = await stageVerifiedPromotion(fixture.input, fixture.dependencies); const result = spawnSync( process.execPath, [path.resolve("scripts/verify-exact-promotion-bundle.ts")], { cwd: fixture.root, encoding: "utf8", env: { ...process.env, PROMOTION_BUNDLE_ROOT: finalized.stagingRoot, VULNERABILITY_PUBLIC_KEY_PATH: fixture.input.vulnerabilityPublicKeyPath, VULNERABILITY_KEY_ID: fixture.input.vulnerabilityKeyId, PROVENANCE_PUBLIC_KEY_PATH: fixture.input.provenancePublicKeyPath, PROVENANCE_KEY_ID: fixture.input.provenanceKeyId, EXPECTED_PROMOTION_RUN_ID: fixture.expectedContext.run.id, EXPECTED_PROMOTION_RUN_ATTEMPT: String(fixture.expectedContext.run.attempt), EXPECTED_PROMOTION_SOURCE_REVISION: fixture.expectedContext.source.revision, EXPECTED_PROMOTION_ARCHIVE_SHA256: fixture.expectedContext.candidate.archiveSha256, EXPECTED_PROMOTION_SOURCE_SET_SHA256: fixture.expectedContext.source.sourceSetSha256, EXPECTED_PROMOTION_BUNDLE_SHA256: fixture.expectedContext.candidate.bundleSha256, EXPECTED_PROMOTION_DIST_SHA256: fixture.expectedContext.candidate.distSha256, EXPECTED_PROMOTION_LOCKFILE_SHA256: fixture.expectedContext.candidate.lockfileSha256, }, }, ); expect(result.error).toBeUndefined(); expect(result.status, result.stderr).toBe(0); expect(result.stdout).toContain("Exact promotion bundle verification: PASS"); await cleanupFinalizedPromotion(finalizedCleanup(fixture, finalized)); }, 30_000); it("rejects every exact-five role, subordinate, digest, and shared-context substitution", async () => { const fixture = await createPromotionStagingFixture(); const finalized = await stageVerifiedPromotion(fixture.input, fixture.dependencies); const valid = Object.fromEntries( await Promise.all( PROMOTED_FILE_NAMES.map(async (name) => [ name, await readFile(path.join(finalized.stagingRoot, name)), ] as const), ), ) as Record<(typeof PROMOTED_FILE_NAMES)[number], Buffer>; const jsonBytes = (value: unknown) => Buffer.from(`${JSON.stringify(value, null, 2)}\n`); const mutateJson = ( name: "provider-verification.json" | "promotion-verification.json", mutate: (value: Record) => void, ) => { const files = { ...valid }; const value = JSON.parse(files[name].toString("utf8")) as Record; mutate(value); files[name] = jsonBytes(value); return files; }; const providerFail = mutateJson("provider-verification.json", (value) => { value.status = "FAIL_UNVERIFIED"; value.vulnerabilityStatus = "FAIL_UNVERIFIED"; value.failures = ["fixture provider failure"]; }); const arbitraryHash = mutateJson("promotion-verification.json", (value) => { value.providerVerificationSha256 = "f".repeat(64); }); const sharedMismatch = mutateJson("promotion-verification.json", (value) => { value.run = { ...value.run, id: "other-run" }; }); const roleSwap = { ...valid, "provider-verification.json": valid["promotion-verification.json"], "promotion-verification.json": valid["provider-verification.json"], }; // `{}` never reaches the digest comparison: it fails the report schema // first, so this case asserted a decode error while claiming to cover the // digest branch. The substitution has to be a structurally valid report // that simply is not the one the verification records committed to. const divergentReport = JSON.parse( valid["vulnerability-report.json"].toString("utf8"), ) as Record; divergentReport.provider = "divergent-provider"; const reportMismatch = { ...valid, "vulnerability-report.json": jsonBytes(divergentReport), }; const absent = { ...valid } as Partial; delete absent["provider-verification.json"]; const jointlyRewritten = { ...valid }; const rewrittenVulnerability = JSON.parse( jointlyRewritten["vulnerability-report.json"].toString("utf8"), ) as Record; rewrittenVulnerability.provider = "attacker-rewritten-provider"; jointlyRewritten["vulnerability-report.json"] = jsonBytes(rewrittenVulnerability); const rewrittenProvider = JSON.parse( jointlyRewritten["provider-verification.json"].toString("utf8"), ) as Record; rewrittenProvider.providerEvidence.vulnerabilityReportSha256 = sha256( jointlyRewritten["vulnerability-report.json"], ); jointlyRewritten["provider-verification.json"] = jsonBytes(rewrittenProvider); const rewrittenPromotion = JSON.parse( jointlyRewritten["promotion-verification.json"].toString("utf8"), ) as Record; rewrittenPromotion.providerEvidence.vulnerabilityReportSha256 = rewrittenProvider.providerEvidence.vulnerabilityReportSha256; rewrittenPromotion.providerVerificationSha256 = sha256( jointlyRewritten["provider-verification.json"], ); jointlyRewritten["promotion-verification.json"] = jsonBytes(rewrittenPromotion); for (const [label, files, diagnostic] of [ ["provider FAIL", providerFail, /provider verification.*PASS/u], ["arbitrary provider hash", arbitraryHash, /provider verification.*hash/u], ["shared context", sharedMismatch, /shared.*run/u], ["role swap", roleSwap, /artifact role/u], ["report digest", reportMismatch, /vulnerability report.*digest/u], ["provider absent", absent, /exact five|missing/u], ["jointly rewritten invalid signature", jointlyRewritten, /signature.*not PASS|signature verification/u], ] as const) { await expect( verifyExactPromotionBundle(files as any, fixture.bundleVerification), label, ).rejects.toThrow(diagnostic); } await cleanupFinalizedPromotion(finalizedCleanup(fixture, finalized)); }, 30_000); it("rejects one Ed25519 key reused for both provider roles", async () => { const fixture = await createPromotionStagingFixture(); await writeFile( fixture.input.provenancePublicKeyPath, await readFile(fixture.input.vulnerabilityPublicKeyPath), ); expect(fixture.input.provenanceKeyId).not.toBe(fixture.input.vulnerabilityKeyId); await expect( stageVerifiedPromotion(fixture.input, fixture.dependencies), ).rejects.toThrow(/distinct|role.*key|same.*key/u); await expect(readdir(fixture.runnerTempRoot)).resolves.toEqual([]); }, 30_000); it("rejects a correctly re-signed secret scan attestation mismatch in the real finalizer", async () => { const fixture = await createPromotionStagingFixture(); const parsed = JSON.parse( await readFile(fixture.input.vulnerabilityReportPath, "utf8"), ) as Record; const { signature: currentSignature, ...unsigned } = parsed; unsigned.secretScanAttestation = { ...unsigned.secretScanAttestation, sarifSha256: "0".repeat(64), }; const value = sign( null, providerEvidenceSignaturePayload(unsigned), fixture.vulnerabilityPrivateKey, ).toString("base64"); await writeFile( fixture.input.vulnerabilityReportPath, `${JSON.stringify({ ...unsigned, signature: { ...currentSignature, value }, })}\n`, ); await expect( stageVerifiedPromotion(fixture.input, fixture.dependencies), ).rejects.toThrow(/secret scan attestation mismatch/u); await expect(readdir(fixture.runnerTempRoot)).resolves.toEqual([]); }, 30_000); it("stages captured archive and reports and validates captured keys after source mutation", async () => { const fixture = await createPromotionStagingFixture(); const finalized = await stageVerifiedPromotion(fixture.input, { ...fixture.dependencies, afterCapture: async () => { await Promise.all([ writeFile(fixture.input.archivePath, "replaced archive\n"), writeFile(fixture.input.vulnerabilityReportPath, "replaced vulnerability\n"), writeFile(fixture.input.provenanceAttestationPath, "replaced provenance\n"), writeFile(fixture.input.vulnerabilityPublicKeyPath, "replaced vulnerability key\n"), writeFile(fixture.input.provenancePublicKeyPath, "replaced provenance key\n"), ]); }, }); for (const [name, bytes] of fixture.capturedSources) { await expect(readFile(path.join(finalized.stagingRoot, name))).resolves.toEqual(bytes); } await cleanupFinalizedPromotion(finalizedCleanup(fixture, finalized)); }, 30_000); it.each([ ["archive digest", async (fixture: Awaited>) => { fixture.input = { ...fixture.input, expectedArchiveSha256: "0".repeat(64) }; }, /archive SHA-256/u], ["report mutation", async (fixture: Awaited>) => { await writeFile(fixture.input.vulnerabilityReportPath, "mutated report\n"); }, /invalid|provider/u], ["key rotation", async (fixture: Awaited>) => { const rotated = generateKeyPairSync("ed25519"); await writeFile(fixture.input.vulnerabilityPublicKeyPath, rotated.publicKey.export({ type: "spki", format: "pem" })); }, /provider evidence failed|trust identity/u], ["expected nonce replay", async (fixture: Awaited>) => { fixture.input = { ...fixture.input, vulnerabilityInvocationNonce: "9".repeat(64) }; }, /invocation nonce/u], ] as const)("rejects %s without staging or PASS records", async (_label, mutate, diagnostic) => { const fixture = await createPromotionStagingFixture(); await mutate(fixture); await expect(stageVerifiedPromotion(fixture.input, fixture.dependencies)).rejects.toThrow(diagnostic); await expect(readdir(fixture.runnerTempRoot)).resolves.toEqual([]); }, 30_000); it("rejects symlinked and oversized captured sources without staging", async () => { const linked = await createPromotionStagingFixture(); const realReport = path.join(linked.root, "real-vulnerability-report.json"); await writeFile(realReport, await readFile(linked.input.vulnerabilityReportPath)); await rm(linked.input.vulnerabilityReportPath); await symlink(realReport, linked.input.vulnerabilityReportPath); await expect(stageVerifiedPromotion(linked.input, linked.dependencies)).rejects.toThrow(/regular file/i); await expect(readdir(linked.runnerTempRoot)).resolves.toEqual([]); const oversized = await createPromotionStagingFixture(); await writeFile(oversized.input.vulnerabilityReportPath, Buffer.alloc(16_777_217, 0x20)); await expect(stageVerifiedPromotion(oversized.input, oversized.dependencies)).rejects.toThrow(/size is outside/i); await expect(readdir(oversized.runnerTempRoot)).resolves.toEqual([]); }, 30_000); it("detects a runner-temp parent identity swap and removes its owned partial staging", async () => { const fixture = await createPromotionStagingFixture(); const displaced = `${fixture.runnerTempRoot}-displaced`; await expect(stageVerifiedPromotion(fixture.input, { ...fixture.dependencies, afterStagingWrite: async () => { await rename(fixture.runnerTempRoot, displaced); await mkdir(fixture.runnerTempRoot, { mode: 0o700 }); }, })).rejects.toThrow(/parent identity changed/u); await expect(readdir(displaced)).resolves.toEqual([]); await expect(readdir(fixture.runnerTempRoot)).resolves.toEqual([]); }, 30_000); it("pins the staging leaf during writes and rejects a replacement at final visibility", async () => { const fixture = await createPromotionStagingFixture(); const visible = path.join(fixture.runnerTempRoot, fixture.cleanupToken); const displaced = `${visible}-displaced`; let writes = 0; await expect( stageVerifiedPromotion(fixture.input, { ...fixture.dependencies, afterFileWrite: async () => { writes += 1; if (writes === 1) { await rename(visible, displaced); await mkdir(visible, { mode: 0o700 }); } }, }), ).rejects.toThrow(/staging leaf identity changed/u); expect(writes).toBe(PROMOTED_FILE_NAMES.length); await expect(readdir(displaced)).resolves.toEqual([]); await expect(readdir(visible)).resolves.toEqual([]); }, 30_000); it("fails and cleans staging when provider evidence expires during staging writes", async () => { const fixture = await createPromotionStagingFixture(); let now = Date.parse("2026-08-02T01:00:00.000Z"); await expect( stageVerifiedPromotion(fixture.input, { ...fixture.dependencies, nowEpochMs: () => now, beforeSeal: async () => { now = Date.parse("2026-08-02T02:00:00.000Z"); }, }), ).rejects.toThrow(/expired|freshness.*not PASS/u); await expect(readdir(fixture.runnerTempRoot)).resolves.toEqual([]); }, 30_000); it("cleanup is token-bound and removes only the finalized private directory", async () => { const fixture = await createPromotionStagingFixture(); const finalized = await stageVerifiedPromotion(fixture.input, fixture.dependencies); const canary = path.join(fixture.runnerTempRoot, "canary"); await writeFile(canary, "unchanged\n"); await expect(cleanupFinalizedPromotion({ runnerTempRoot: fixture.runnerTempRoot, stagingRoot: finalized.stagingRoot, cleanupToken: `${finalized.cleanupToken}-wrong`, runnerTempIdentity: finalized.runnerTempIdentity, stagingIdentity: finalized.stagingIdentity, })).rejects.toThrow(/root\/token mismatch/u); await cleanupFinalizedPromotion(finalizedCleanup(fixture, finalized)); await expect(readFile(canary, "utf8")).resolves.toBe("unchanged\n"); await expect(lstat(finalized.stagingRoot)).rejects.toMatchObject({ code: "ENOENT" }); }, 30_000); it("cleanup rejects a swapped runner-temp ancestor and a symlinked staging leaf", async () => { const swapped = await createPromotionStagingFixture(); const finalized = await stageVerifiedPromotion(swapped.input, swapped.dependencies); const displaced = `${swapped.runnerTempRoot}-cleanup-displaced`; await expect(cleanupFinalizedPromotion(finalizedCleanup(swapped, finalized), { beforeRemove: async () => { await rename(swapped.runnerTempRoot, displaced); await mkdir(swapped.runnerTempRoot, { mode: 0o700 }); }, })).rejects.toThrow(/parent identity changed/u); await expect(readdir(swapped.runnerTempRoot)).resolves.toEqual([]); await expect(readdir(displaced)).resolves.toEqual([finalized.cleanupToken]); const linked = await createPromotionStagingFixture(); const linkedFinalized = await stageVerifiedPromotion(linked.input, linked.dependencies); const saved = `${linkedFinalized.stagingRoot}-saved`; const outside = await temporaryRoot("promotion-cleanup-outside-"); const canary = path.join(outside, "canary"); await writeFile(canary, "unchanged\n"); await expect( cleanupFinalizedPromotion(finalizedCleanup(linked, linkedFinalized), { beforeRemove: async () => { await rename(linkedFinalized.stagingRoot, saved); await symlink(outside, linkedFinalized.stagingRoot); }, }), ).rejects.toThrow(/leaf.*identity|staging leaf/u); await expect(readFile(canary, "utf8")).resolves.toBe("unchanged\n"); // A substituted leaf aborts the cleanup before anything is removed, so the // promotion this call owned is still intact and a retry sees a coherent // directory rather than a half-emptied one. expect((await readdir(saved)).sort()).toEqual([...PROMOTED_FILE_NAMES].sort()); }, 30_000); it("never deletes an unrelated leaf substituted after cleanup validation", async () => { const fixture = await createPromotionStagingFixture(); const finalized = await stageVerifiedPromotion(fixture.input, fixture.dependencies); const displaced = `${finalized.stagingRoot}-owned`; const canary = path.join(finalized.stagingRoot, "unrelated-canary"); await expect(cleanupFinalizedPromotion(finalizedCleanup(fixture, finalized), { beforeRemove: async () => { await rename(finalized.stagingRoot, displaced); await mkdir(finalized.stagingRoot, { mode: 0o700 }); await writeFile(canary, "must survive\n"); }, })).rejects.toThrow(/identity changed|substituted/u); await expect(readFile(canary, "utf8")).resolves.toBe("must survive\n"); await expect(readdir(displaced)).resolves.toEqual( expect.arrayContaining([...PROMOTED_FILE_NAMES]), ); }, 30_000); }); type ProviderSupervisorInput = Readonly<{ kind?: "vulnerability" | "provenance"; command: string; reportPath?: string; sealedPath: string; environment?: Readonly>; spawnTimeoutMs?: number; maxBuffer?: number; }>; function runProviderSupervisor( fixture: Awaited>, input: ProviderSupervisorInput, ) { const kind = input.kind ?? "vulnerability"; return spawnSync( process.execPath, [path.resolve("scripts/run-and-validate-provider.ts"), "--kind", kind], { cwd: fixture.root, encoding: "utf8", timeout: input.spawnTimeoutMs ?? 8_000, maxBuffer: input.maxBuffer, env: providerSupervisorEnvironment(fixture, input), }, ); } function startProviderSupervisor( fixture: Awaited>, input: ProviderSupervisorInput, ): Readonly<{ child: ChildProcess; completion: Promise>; }> { const kind = input.kind ?? "vulnerability"; const child = spawn( process.execPath, [path.resolve("scripts/run-and-validate-provider.ts"), "--kind", kind], { cwd: fixture.root, env: providerSupervisorEnvironment(fixture, input), stdio: ["ignore", "pipe", "pipe"], }, ); let stderr = ""; let stdout = ""; child.stderr?.setEncoding("utf8"); child.stdout?.setEncoding("utf8"); child.stderr?.on("data", (chunk: string) => { stderr += chunk; }); child.stdout?.on("data", (chunk: string) => { stdout += chunk; }); const completion = new Promise>((resolve, reject) => { child.once("error", reject); child.once("close", (code, signal) => resolve({ code, signal, stderr, stdout })); }); return Object.freeze({ child, completion }); } function providerSupervisorEnvironment( fixture: Awaited>, input: ProviderSupervisorInput, ): NodeJS.ProcessEnv { const kind = input.kind ?? "vulnerability"; const reportPath = input.reportPath ?? fixture.reportPath; return { ...process.env, ...input.environment, ...(kind === "vulnerability" ? { VULNERABILITY_PROVIDER_COMMAND: input.command, VULNERABILITY_PROVIDER_PRIVATE_KEY_PATH: fixture.privateKeyPath, VULNERABILITY_PUBLIC_KEY_PATH: fixture.publicKeyPath, VULNERABILITY_KEY_ID: fixture.keyId, VULNERABILITY_REPORT_PATH: reportPath, } : { PROVENANCE_PROVIDER_COMMAND: input.command, PROVENANCE_PROVIDER_PRIVATE_KEY_PATH: fixture.privateKeyPath, PROVENANCE_PUBLIC_KEY_PATH: fixture.publicKeyPath, PROVENANCE_KEY_ID: fixture.keyId, PROVENANCE_ATTESTATION_PATH: reportPath, }), VALIDATED_PROVIDER_REPORT_PATH: input.sealedPath, CANDIDATE_ARCHIVE_PATH: fixture.archivePath, CANDIDATE_ARCHIVE_SHA256: fixture.archiveSha256, CI_RUN_ID: fixture.expectedContext.run.id, CI_RUN_ATTEMPT: String(fixture.expectedContext.run.attempt), EXPECTED_SOURCE_REVISION: fixture.expectedContext.source.revision, }; } async function waitForProviderUnit( kind: "vulnerability" | "provenance", supervisorPid: number | undefined, ): Promise { if (!supervisorPid) throw new Error("provider supervisor did not expose its PID"); const pattern = `ca-provider-${kind}-${supervisorPid}-*.scope`; for (let attempt = 0; attempt < 120; attempt += 1) { const units = listProviderUnits(pattern); if (units.length === 1) return units[0]!; if (units.length > 1) throw new Error(`provider unit identity is ambiguous: ${units.join(", ")}`); await delay(25); } throw new Error(`provider cgroup unit did not become active: ${pattern}`); } async function expectProviderUnitGone(unitName: string): Promise { for (let attempt = 0; attempt < 120; attempt += 1) { if (listProviderUnits(unitName).length === 0) return; await delay(25); } throw new Error(`provider scope survived completion: ${unitName}`); } async function waitForFileContent(filename: string, expected: string): Promise { for (let attempt = 0; attempt < 120; attempt += 1) { try { if ((await readFile(filename, "utf8")) === expected) return; } catch (error) { if (!hasErrorCode(error, "ENOENT")) throw error; } await delay(25); } throw new Error(`provider output did not reach expected content: ${filename}`); } async function readCgroupPids(cgroupRoot: string): Promise { return (await readFile(path.join(cgroupRoot, "cgroup.procs"), "utf8")) .trim().split("\n").filter(Boolean).map(Number); } async function waitForRecordedProviderTree( tree: ReturnType, providerScript: string, ): Promise { for (let attempt = 0; attempt < 400; attempt += 1) { const children = tree.directChildArguments(); const members = tree.cgroupArguments(); // Every process the assertions below reason about. Returning as soon as // some of them are present is what left bubblewrap out of the recording: // it enters the scope a few milliseconds after the wrapper does. if ( children.some((value) => value.includes("/usr/bin/systemd-run")) && children.some((value) => value.includes("provider-raw-guardian.ts")) && members.some((value) => value.includes("provider-scope-wrapper.ts")) && members.some((value) => /\/usr\/bin\/(?:bwrap|prlimit)/u.test(value)) && members.some((value) => value.includes(providerScript)) ) { return; } await delay(10); } throw new Error( "the provider run never contained a systemd-run child, a guardian child, " + "a scope wrapper, a sandbox and the provider itself in its cgroup", ); } async function waitForDirectChildMatching(supervisorPid: number, pattern: string): Promise { for (let attempt = 0; attempt < 120; attempt += 1) { const children = (await readFile(`/proc/${supervisorPid}/task/${supervisorPid}/children`, "utf8")) .trim().split(/\s+/u).filter(Boolean).map(Number); for (const pid of children) { if (processExists(pid) && showProcessArguments(pid).includes(pattern)) return pid; } await delay(25); } throw new Error(`provider supervisor child was not observable: ${pattern}`); } async function waitForExistingPath(target: string): Promise { for (let attempt = 0; attempt < 120; attempt += 1) { try { await lstat(target); return; } catch (error) { if (!hasErrorCode(error, "ENOENT")) throw error; } await delay(25); } throw new Error(`expected path did not appear: ${target}`); } async function waitForMissingPath(target: string): Promise { for (let attempt = 0; attempt < 120; attempt += 1) { try { await lstat(target); } catch (error) { if (hasErrorCode(error, "ENOENT")) return; throw error; } await delay(25); } throw new Error(`expected path survived: ${target}`); } async function waitForProcessGone(pid: number): Promise { for (let attempt = 0; attempt < 120; attempt += 1) { if (!processExists(pid)) return; await delay(25); } throw new Error(`expected process survived: ${pid}`); } async function findProviderRawReferences( reportPath: string, identity: Readonly<{ dev: number; ino: number }>, ): Promise { const references: string[] = []; const processDirectories = (await readdir("/proc", { withFileTypes: true })) .filter((entry) => entry.isDirectory() && /^[1-9][0-9]*$/u.test(entry.name)) .map((entry) => entry.name); for (const pid of processDirectories) { let descriptors: string[]; try { descriptors = await readdir(`/proc/${pid}/fd`); } catch (error) { if (isTransientProcError(error)) continue; throw error; } for (const descriptor of descriptors) { try { const metadata = await stat(`/proc/${pid}/fd/${descriptor}`); if (metadata.dev === identity.dev && metadata.ino === identity.ino) { references.push(`${pid}/fd/${descriptor}`); } } catch (error) { if (!isTransientProcError(error)) throw error; } } try { const mountInfo = await readFile(`/proc/${pid}/mountinfo`, "utf8"); if (mountInfo.includes(reportPath)) references.push(`${pid}/mountinfo`); } catch (error) { if (!isTransientProcError(error)) throw error; } } return references.sort(); } function isTransientProcError(error: unknown): boolean { return hasErrorCode(error, "EACCES") || hasErrorCode(error, "ENOENT") || hasErrorCode(error, "EPERM"); } function processExists(pid: number): boolean { try { process.kill(pid, 0); return true; } catch (error) { if (hasErrorCode(error, "ESRCH")) return false; if (hasErrorCode(error, "EPERM")) return true; throw error; } } function listProviderUnits(pattern: string): string[] { const result = spawnSync( "/usr/bin/systemctl", ["--user", "list-units", "--all", "--type=scope", "--plain", "--no-legend", pattern], { encoding: "utf8", maxBuffer: 65_536, timeout: 2_000 }, ); if (result.error) throw result.error; if (result.status !== 0) throw new Error(`systemctl list-units failed: ${result.stderr.trim()}`); return result.stdout .split("\n") .map((line) => line.trim().split(/\s+/u)[0] ?? "") .filter(Boolean); } function showProviderUnit(unitName: string): Readonly> { const properties = [ "ActiveState", "ControlGroup", "CPUQuotaPerSecUSec", "CPUQuotaPeriodUSec", "KillMode", "MemoryMax", "MemorySwapMax", "SendSIGKILL", "TasksMax", ]; const result = spawnSync( "/usr/bin/systemctl", ["--user", "show", unitName, ...properties.map((name) => `--property=${name}`)], { encoding: "utf8", maxBuffer: 65_536, timeout: 2_000 }, ); if (result.error) throw result.error; if (result.status !== 0) throw new Error(`systemctl show failed: ${result.stderr.trim()}`); return Object.freeze(Object.fromEntries(result.stdout.trim().split("\n").map((line) => { const separator = line.indexOf("="); return [line.slice(0, separator), line.slice(separator + 1)]; }))); } function readProviderUnitMetadata(unitName: string): string { const result = spawnSync( "/usr/bin/systemctl", ["--user", "show", unitName], { encoding: "utf8", maxBuffer: 262_144, timeout: 2_000 }, ); if (result.error) throw result.error; if (result.status !== 0) throw new Error(`systemctl show failed: ${result.stderr.trim()}`); return result.stdout; } function showProcessArguments(pid: number | undefined): string { if (!pid) throw new Error("provider supervisor did not expose its PID"); const argv = readProcessArguments(pid); if (argv !== null) return argv; const result = spawnSync( "/usr/bin/ps", ["-o", "args=", "-p", String(pid)], { encoding: "utf8", maxBuffer: 65_536, timeout: 2_000 }, ); if (result.error) throw result.error; if (result.status !== 0) throw new Error(`ps failed: ${result.stderr.trim()}`); return result.stdout.trim(); } /** * Reads a process's argv straight from `/proc`. * * Spawning `ps` per pid costs milliseconds each, and the provider sandbox now * completes a whole run in well under a second — the observation was losing a * race against the thing it was observing. Returns null for a process that is * already gone, which the sampler treats as "nothing more to record". */ function readProcessArguments(pid: number): string | null { try { return readFileSync(`/proc/${pid}/cmdline`, "utf8") .split("\0") .filter(Boolean) .join(" ") .trim(); } catch { return null; } } /** * Records the provider's process tree for the whole life of the run. * * The assertions below are about what the sandbox looked like while it was * running, and a single snapshot taken afterwards can only ever be a guess at * that. Sampling from the moment the supervisor starts turns "did we look at * the right instant?" into "what did this run actually contain?". */ function recordProviderProcessTree(supervisorPid: number) { const directChildren = new Map(); const cgroupMembers = new Map(); let stopped = false; const remember = (into: Map, pid: number): void => { if (into.has(pid)) return; const argv = readProcessArguments(pid); if (argv !== null && argv.length > 0) into.set(pid, argv); }; const childrenOf = (pid: number): number[] => { try { return readFileSync(`/proc/${pid}/task/${pid}/children`, "utf8") .trim() .split(/\s+/u) .filter(Boolean) .map(Number); } catch { return []; } }; /** * Scope membership is read from the process itself rather than from * `systemctl show`. Waiting for the unit to be described before watching its * cgroup meant bubblewrap had usually already exited by the time the first * sample was taken — the recording missed exactly the process the assertions * are about. */ const inProviderScope = (pid: number): boolean => { try { return readFileSync(`/proc/${pid}/cgroup`, "utf8").includes("ca-provider-"); } catch { return false; } }; const sample = (): void => { if (stopped) return; const direct = childrenOf(supervisorPid); for (const pid of direct) remember(directChildren, pid); // Walk the whole subtree: the scope wrapper, bubblewrap and the provider // itself sit below systemd-run, not beside it. const pending = [...direct]; const seen = new Set(direct); while (pending.length > 0 && seen.size < 512) { const pid = pending.pop()!; if (inProviderScope(pid)) remember(cgroupMembers, pid); for (const child of childrenOf(pid)) { if (seen.has(child)) continue; seen.add(child); pending.push(child); } } }; const timer = setInterval(sample, 5); timer.unref(); sample(); return Object.freeze({ stop() { stopped = true; clearInterval(timer); }, directChildPids: () => [...directChildren.keys()], directChildArguments: () => [...directChildren.values()], cgroupPids: () => [...cgroupMembers.keys()], cgroupArguments: () => [...cgroupMembers.values()], }); } async function startLoopbackCanary(root: string): Promise> { const portFile = path.join(root, `loopback-port-${Date.now()}`); const marker = `${portFile}-reached`; const child = spawn(process.execPath, ["-e", [ "const {createServer}=require('node:http');", "const {writeFileSync}=require('node:fs');", `const server=createServer((_request,response)=>{writeFileSync(${JSON.stringify(marker)},'reached\\n');response.end('ok');});`, `server.listen(0,'127.0.0.1',()=>writeFileSync(${JSON.stringify(portFile)},String(server.address().port)));`, ].join("\n")], { stdio: "ignore" }); for (let attempt = 0; attempt < 80; attempt += 1) { try { const port = Number(await readFile(portFile, "utf8")); if (Number.isInteger(port) && port > 0) return Object.freeze({ child, marker, port }); } catch (error) { if (!hasErrorCode(error, "ENOENT")) throw error; } await delay(25); } child.kill("SIGKILL"); await waitForChildClose(child); throw new Error("loopback canary did not become ready"); } async function waitForChildClose(child: ChildProcess): Promise { if (child.exitCode !== null || child.signalCode !== null) return; await new Promise((resolve) => child.once("close", () => resolve())); } function hasErrorCode(error: unknown, code: string): boolean { return Boolean(error && typeof error === "object" && "code" in error && error.code === code); } function providerValidationInput( fixture: Awaited>, capturedReport: Buffer = fixture.reportBytes, ) { return { kind: "vulnerability" as const, verifiedManifest: fixture.candidate, archiveSha256: fixture.archiveSha256, candidateRoot: fixture.candidateRoot, capturedReport, expectedContext: fixture.expectedContext, trust: fixture.trust, nowEpochMs: () => fixture.now, }; } function providerV2WriterSource( options: Readonly<{ importFs?: boolean; kind?: "vulnerability" | "provenance"; }> = {}, ): string { const providerKind = options.kind ?? "vulnerability"; const privateKeyEnvironment = providerKind === "vulnerability" ? "VULNERABILITY_PROVIDER_PRIVATE_KEY_PATH" : "PROVENANCE_PROVIDER_PRIVATE_KEY_PATH"; const reportEnvironment = providerKind === "vulnerability" ? "VULNERABILITY_REPORT_PATH" : "PROVENANCE_ATTESTATION_PATH"; return [ "import { createPrivateKey, sign } from 'node:crypto';", ...(options.importFs === false ? [] : ["import { readFileSync, writeFileSync } from 'node:fs';"]), "const canonical = (value) => {", " if (Array.isArray(value)) return value.map(canonical).sort((left, right) => String(JSON.stringify(left)).localeCompare(String(JSON.stringify(right))));", " if (value && typeof value === 'object') return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key, canonical(item)]));", " return value;", "};", "const unsigned = {", " schemaVersion: Number(process.env.PROVIDER_EVIDENCE_SCHEMA_VERSION),", " evidenceType: process.env.PROVIDER_EVIDENCE_TYPE,", " provider: 'fixture',", " issuedAt: process.env.PROVIDER_ISSUED_AT,", " expiresAt: process.env.PROVIDER_EXPIRES_AT,", " run: { id: process.env.CI_RUN_ID, attempt: Number(process.env.CI_RUN_ATTEMPT), invocationNonce: process.env.PROVIDER_INVOCATION_NONCE },", " source: { revision: process.env.SOURCE_REVISION, sourceSetSha256: process.env.SOURCE_SET_SHA256 },", " candidate: { archiveSha256: process.env.CANDIDATE_ARCHIVE_SHA256, bundleSha256: process.env.CANDIDATE_BUNDLE_SHA256, distSha256: process.env.CANDIDATE_DIST_SHA256, lockfileSha256: process.env.CANDIDATE_LOCKFILE_SHA256 },", ...(providerKind === "vulnerability" ? [ " secretScanAttestation: { status: process.env.SECRET_SCAN_STATUS, localEvidenceAssessmentSha256: process.env.SECRET_SCAN_LOCAL_EVIDENCE_ASSESSMENT_SHA256, sourceSetSha256: process.env.SECRET_SCAN_SOURCE_SET_SHA256, policySha256: process.env.SECRET_SCAN_POLICY_SHA256, sarifSha256: process.env.SECRET_SCAN_SARIF_SHA256, scanInputSha256: process.env.SECRET_SCAN_INPUT_SHA256 },", " findings: [],", ] : [ " signer: 'fixture-signer',", " subject: { name: 'dist', digest: { sha256: process.env.CANDIDATE_DIST_SHA256 } },", ]), "};", `const privateKey = createPrivateKey(readFileSync(process.env.${privateKeyEnvironment}));`, "const value = sign(null, Buffer.from(JSON.stringify(canonical(unsigned))), privateKey).toString('base64');", `writeFileSync(process.env.${reportEnvironment}, \`\${JSON.stringify({ ...unsigned, signature: { algorithm: 'Ed25519', keyId: process.env.PROVIDER_KEY_ID, publicKeyFingerprint: process.env.PROVIDER_PUBLIC_KEY_FINGERPRINT, value } })}\\n\`);`, ].join("\n"); } async function createCandidateArchiveFixture( options: Readonly<{ extraMember?: boolean; repeatedExtraMembers?: number; duplicateMember?: boolean; oversizedManifest?: boolean }> = {}, ): Promise> { const root = await temporaryRoot("ci-candidate-archive-"); const files = new Map(); files.set("dist/app.js", Buffer.from("app\n")); for (const evidencePath of RELEASE_CANDIDATE_EVIDENCE_PATHS) { files.set(evidencePath, Buffer.from(`${evidencePath}\n`)); } for (const [relative, content] of files) await writeArtifact(root, relative, content); await writeArtifact( root, "artifacts/release/dependency-inventory.json", `${JSON.stringify({ lockfileSha256: sha256(files.get("pnpm-lock.yaml")!) })}\n`, ); const manifest = await createReleaseCandidateManifest(root); await writeArtifact( root, RELEASE_CANDIDATE_MANIFEST_PATH, `${JSON.stringify(manifest)}${options.oversizedManifest ? " ".repeat(8_388_609) : "\n"}`, ); if (options.extraMember || options.repeatedExtraMembers) { await writeArtifact(root, "extra.txt", "extra\n"); } const archivePath = path.join(root, "candidate.tar.gz"); const members = [ "dist", ...RELEASE_CANDIDATE_EVIDENCE_PATHS, RELEASE_CANDIDATE_MANIFEST_PATH, ...(options.duplicateMember ? ["pnpm-lock.yaml"] : []), ...(options.extraMember ? ["extra.txt"] : []), ...Array.from({ length: options.repeatedExtraMembers ?? 0 }, () => "extra.txt"), ]; const tar = spawnSync("tar", [...(options.duplicateMember ? ["--hard-dereference"] : []), "-czf", archivePath, ...members], { cwd: root, encoding: "utf8", }); if (tar.status !== 0) throw new Error(tar.stderr); return { root, archivePath }; } async function createProviderFixture() { const base = await ensureProviderBaseFixture(); const root = await temporaryRoot("ci-provider-upload-"); await cp(base, root, { recursive: true }); const candidateRoot = root; const candidate = JSON.parse( await readFile(path.join(root, RELEASE_CANDIDATE_MANIFEST_PATH), "utf8"), ) as Awaited>; const protectedCandidateRelative = candidate.files.find((file) => file.path.startsWith("dist/"), )?.path; if (!protectedCandidateRelative) { throw new Error("provider fixture candidate has no dist file"); } const protectedCandidatePath = path.join(root, protectedCandidateRelative); const protectedCandidateBytes = await readFile(protectedCandidatePath); const assessment = localEvidenceAssessmentArtifactSchema.parse( JSON.parse( await readFile(path.join(root, LOCAL_EVIDENCE_ASSESSMENT_PATH), "utf8"), ) as unknown, ); const archivePath = path.join(root, "candidate.tar.gz"); const archiveSha256 = sha256(await readFile(archivePath)); const keys = generateKeyPairSync("ed25519"); const keyId = "fixture-vulnerability-key"; const publicKeyPath = path.join(root, "keys/vulnerability.pem"); const privateKeyPath = path.join(root, "keys/vulnerability-private.pem"); await writeArtifact( root, "keys/vulnerability.pem", keys.publicKey.export({ type: "spki", format: "pem" }), ); await writeArtifact( root, "keys/vulnerability-private.pem", keys.privateKey.export({ type: "pkcs8", format: "pem" }), ); const trust = (await readProviderTrust(root, publicKeyPath, keyId))!; const now = Date.parse("2026-08-02T01:00:00.000Z"); const expectedContext = { run: { id: "fixture-run", attempt: 1 }, source: { revision: assessment.source.revision, sourceSetSha256: assessment.source.sourceSetSha256, }, candidate: { archiveSha256, bundleSha256: candidate.bundleSha256, distSha256: candidate.distSha256, lockfileSha256: candidate.lockfileSha256, }, vulnerabilityInvocationNonce: "1".repeat(64), provenanceInvocationNonce: "0".repeat(64), secretScanAttestation: { status: "PASS" as const, localEvidenceAssessmentSha256: sha256( await readFile(path.join(root, LOCAL_EVIDENCE_ASSESSMENT_PATH)), ), sourceSetSha256: assessment.source.sourceSetSha256, policySha256: assessment.secretScan.policySha256, sarifSha256: assessment.secretScan.sarifSha256, scanInputSha256: assessment.secretScan.scanInputSha256, }, } as const; const unsigned = { schemaVersion: 2 as const, evidenceType: "vulnerability-report" as const, provider: "fixture", issuedAt: "2026-08-02T01:00:00.000Z", expiresAt: "2026-08-02T02:00:00.000Z", run: { ...expectedContext.run, invocationNonce: expectedContext.vulnerabilityInvocationNonce }, source: expectedContext.source, candidate: expectedContext.candidate, secretScanAttestation: expectedContext.secretScanAttestation, findings: [], }; const report = { ...unsigned, signature: { algorithm: "Ed25519" as const, keyId, publicKeyFingerprint: providerPublicKeyFingerprint(keys.publicKey), value: sign( null, providerEvidenceSignaturePayload(unsigned), keys.privateKey, ).toString("base64"), }, }; const reportPath = path.join( root, "provider-evidence/untrusted/vulnerability-report.json", ); await writeArtifact( root, "provider-evidence/untrusted/vulnerability-report.json", `${JSON.stringify(report)}\n`, ); const reportBytes = Buffer.from(`${JSON.stringify(report)}\n`); return { root, candidateRoot, candidate, protectedCandidatePath, protectedCandidateBytes, archivePath, archiveSha256, reportPath, reportBytes, expectedContext, trust, now, publicKeyPath, privateKeyPath, privateKey: keys.privateKey, keyId, providerWriter: path.join(root, "provider-v2-writer.mjs"), }; } async function ensureProviderBaseFixture(): Promise { if (providerBaseRoot) return providerBaseRoot; const sourceRoot = process.cwd(); const root = await mkdtemp(path.join(tmpdir(), "ci-provider-v2-base-")); await cp(sourceRoot, root, { recursive: true, filter: (source) => { const relative = path.relative(sourceRoot, source); if (!relative) return true; const first = relative.split(path.sep)[0]; return ![".release", "artifacts", "dist", "node_modules"].includes(first ?? ""); }, }); // The release evidence, minus the trace and Storybook trees. Copying the // whole `artifacts/` directory pulled ~28MB of test output into every // provider fixture; sharing the copier with the removal fixture is also what // makes both fixtures contain the same evidence. // `artifacts/release` travels with the fixture rather than being deleted and // rebuilt. Supply-chain generation runs before the candidate is created and // validates the release evidence paths, so deleting them made that step // depend on a previous local run having left them behind — which is why this // fixture only worked in a workspace that had already built a candidate. The // build chain overwrites them anyway. await copyReleaseEvidenceTree(sourceRoot, root); await linkFixtureNodeModules(root, sourceRoot); const git = spawnSync("git", ["show", "-s", "--format=%H%n%ct", "HEAD"], { cwd: sourceRoot, encoding: "utf8", }); if (git.status !== 0) throw new Error(git.stderr); const [revision, sourceDateEpoch] = git.stdout.trim().split(/\r?\n/u); const build = spawnSync("corepack", ["pnpm", "build:release-candidate"], { cwd: root, encoding: "utf8", timeout: 120_000, maxBuffer: 32 * 1024 * 1024, env: { ...process.env, CI: "true", VITE_BUILD_ID: "provider-v2-fixture", VITE_COMMIT_SHA: revision, RELEASE_ID: "provider-v2-fixture", SOURCE_DATE_EPOCH: sourceDateEpoch, CI_RUNNER_IMAGE: `fixture@sha256:${"a".repeat(64)}`, }, }); if (build.status !== 0) throw new Error(`${build.stdout}\n${build.stderr}`); const archivePath = path.join(root, "candidate.tar.gz"); const tar = spawnSync( "/usr/bin/tar", [ "--sort=name", "--mtime=@0", "--owner=0", "--group=0", "--numeric-owner", "-czf", archivePath, "dist", ...RELEASE_CANDIDATE_EVIDENCE_PATHS, RELEASE_CANDIDATE_MANIFEST_PATH, ], { cwd: root, encoding: "utf8" }, ); if (tar.status !== 0) throw new Error(tar.stderr); providerBaseRoot = root; return root; } async function createPromotionStagingFixture( options: Readonly<{ nowEpochMs?: number }> = {}, ) { const base = await ensureProviderBaseFixture(); const root = await temporaryRoot("promotion-finalizer-"); const archivePath = path.join(root, "inputs/release-candidate.tar.gz"); await mkdir(path.dirname(archivePath), { recursive: true }); await cp(path.join(base, "candidate.tar.gz"), archivePath); const candidate = await verifyCiCandidateArchive({ archivePath }); const candidateArchiveBytes = await readFile(archivePath); const assessmentBytes = await readFile(path.join(base, LOCAL_EVIDENCE_ASSESSMENT_PATH)); const assessment = localEvidenceAssessmentArtifactSchema.parse( JSON.parse(assessmentBytes.toString("utf8")) as unknown, ); const vulnerabilityKeys = generateKeyPairSync("ed25519"); const provenanceKeys = generateKeyPairSync("ed25519"); const vulnerabilityKeyId = "fixture-vulnerability"; const provenanceKeyId = "fixture-provenance"; const vulnerabilityInvocationNonce = "5".repeat(64); const provenanceInvocationNonce = "6".repeat(64); const fixtureNow = options.nowEpochMs ?? Date.parse("2026-08-02T01:00:00.000Z"); const issuedAt = new Date(fixtureNow).toISOString(); const expiresAt = new Date(fixtureNow + 60 * 60 * 1_000).toISOString(); const expectedContext = { run: { id: "fixture-run", attempt: 1 }, source: { revision: assessment.source.revision, sourceSetSha256: assessment.source.sourceSetSha256, }, candidate: { archiveSha256: sha256(candidateArchiveBytes), bundleSha256: candidate.manifest.bundleSha256, distSha256: candidate.manifest.distSha256, lockfileSha256: candidate.manifest.lockfileSha256, }, secretScanAttestation: { status: "PASS" as const, localEvidenceAssessmentSha256: sha256(assessmentBytes), sourceSetSha256: assessment.source.sourceSetSha256, policySha256: assessment.secretScan.policySha256, sarifSha256: assessment.secretScan.sarifSha256, scanInputSha256: assessment.secretScan.scanInputSha256, }, } as const; const vulnerabilityUnsigned = { schemaVersion: 2 as const, evidenceType: "vulnerability-report" as const, provider: "fixture-vulnerability", issuedAt, expiresAt, run: { ...expectedContext.run, invocationNonce: vulnerabilityInvocationNonce }, source: expectedContext.source, candidate: expectedContext.candidate, secretScanAttestation: expectedContext.secretScanAttestation, findings: [], }; const vulnerabilityReport = { ...vulnerabilityUnsigned, signature: { algorithm: "Ed25519" as const, keyId: vulnerabilityKeyId, publicKeyFingerprint: providerPublicKeyFingerprint(vulnerabilityKeys.publicKey), value: sign( null, providerEvidenceSignaturePayload(vulnerabilityUnsigned), vulnerabilityKeys.privateKey, ).toString("base64"), }, }; const provenanceUnsigned = { schemaVersion: 2 as const, evidenceType: "provenance-attestation" as const, provider: "fixture-provenance", signer: "fixture-signer", issuedAt, expiresAt, run: { ...expectedContext.run, invocationNonce: provenanceInvocationNonce }, source: expectedContext.source, candidate: expectedContext.candidate, subject: { name: "dist" as const, digest: { sha256: candidate.manifest.distSha256 }, }, }; const provenanceAttestation = { ...provenanceUnsigned, signature: { algorithm: "Ed25519" as const, keyId: provenanceKeyId, publicKeyFingerprint: providerPublicKeyFingerprint(provenanceKeys.publicKey), value: sign( null, providerEvidenceSignaturePayload(provenanceUnsigned), provenanceKeys.privateKey, ).toString("base64"), }, }; const vulnerabilityReportBytes = Buffer.from(`${JSON.stringify(vulnerabilityReport)}\n`); const provenanceAttestationBytes = Buffer.from(`${JSON.stringify(provenanceAttestation)}\n`); const vulnerabilityReportPath = path.join( root, "inputs/vulnerability-report.json", ); const provenanceAttestationPath = path.join( root, "inputs/provenance-attestation.json", ); const vulnerabilityPublicKeyPath = path.join(root, "keys/vulnerability.pem"); const provenancePublicKeyPath = path.join(root, "keys/provenance.pem"); await writeArtifact( root, "inputs/vulnerability-report.json", vulnerabilityReportBytes, ); await writeArtifact( root, "inputs/provenance-attestation.json", provenanceAttestationBytes, ); await writeArtifact( root, "keys/vulnerability.pem", vulnerabilityKeys.publicKey.export({ type: "spki", format: "pem" }), ); await writeArtifact( root, "keys/provenance.pem", provenanceKeys.publicKey.export({ type: "spki", format: "pem" }), ); const runnerTempRoot = path.join(root, "runner-temp"); await mkdir(runnerTempRoot, { mode: 0o700 }); const cleanupToken = `promotion-fixture-run-1-${"2a".repeat(16)}`; const expectedProviderEvidence = { vulnerabilityReportSha256: sha256(vulnerabilityReportBytes), provenanceAttestationSha256: sha256(provenanceAttestationBytes), vulnerabilityInvocationNonce, provenanceInvocationNonce, vulnerabilityKeyId, vulnerabilityKeyFingerprint: providerPublicKeyFingerprint(vulnerabilityKeys.publicKey), provenanceKeyId, provenanceKeyFingerprint: providerPublicKeyFingerprint(provenanceKeys.publicKey), secretScanAttestation: expectedContext.secretScanAttestation, }; return { root, runnerTempRoot, cleanupToken, assessmentSha256: sha256(assessmentBytes), expectedContext, expectedProviderEvidence, vulnerabilityPrivateKey: vulnerabilityKeys.privateKey, bundleVerification: { vulnerabilityTrust: { keyId: vulnerabilityKeyId, publicKey: vulnerabilityKeys.publicKey, publicKeyFingerprint: providerPublicKeyFingerprint(vulnerabilityKeys.publicKey), }, provenanceTrust: { keyId: provenanceKeyId, publicKey: provenanceKeys.publicKey, publicKeyFingerprint: providerPublicKeyFingerprint(provenanceKeys.publicKey), }, expected: { run: expectedContext.run, sourceRevision: expectedContext.source.revision, sourceSetSha256: expectedContext.source.sourceSetSha256, archiveSha256: expectedContext.candidate.archiveSha256, bundleSha256: expectedContext.candidate.bundleSha256, distSha256: expectedContext.candidate.distSha256, lockfileSha256: expectedContext.candidate.lockfileSha256, }, nowEpochMs: () => fixtureNow, }, capturedSources: new Map([ ["release-candidate.tar.gz", candidateArchiveBytes], ["vulnerability-report.json", vulnerabilityReportBytes], ["provenance-attestation.json", provenanceAttestationBytes], ]), dependencies: { nowEpochMs: () => fixtureNow, randomBytes: (bytes: number) => Buffer.alloc(bytes, 0x2a), }, input: { repositoryRoot: root, archivePath, expectedArchiveSha256: sha256(candidateArchiveBytes), vulnerabilityReportPath, provenanceAttestationPath, vulnerabilityPublicKeyPath, vulnerabilityKeyId, provenancePublicKeyPath, provenanceKeyId, expectedRun: { id: expectedContext.run.id, attempt: expectedContext.run.attempt, sourceRevision: expectedContext.source.revision, }, vulnerabilityInvocationNonce, provenanceInvocationNonce, runnerTempRoot, }, }; } function finalizedCleanup( fixture: Awaited>, finalized: Awaited>, ) { return { runnerTempRoot: fixture.runnerTempRoot, stagingRoot: finalized.stagingRoot, cleanupToken: finalized.cleanupToken, runnerTempIdentity: finalized.runnerTempIdentity, stagingIdentity: finalized.stagingIdentity, }; }