Files
clean-architecture-frontend…/tests/unit/ci-artifact-contract.test.ts
T
DongHyeonkaandClaude Opus 5 10a04d3695 fix: let a removal fixture actually build the thing it claims still builds
FE-GATE-020 proves a capability can be removed by rebuilding the whole project
without it. The fixture it built could not get that far, and the failures all
came from the fixture rather than from anything about removability.

It was not a repository. The supply-chain inventory is defined as the tracked
file set, so it asks `git ls-files` what the project contains; with no
repository to ask, generation failed and took every provider suite down with
it. It is now initialised on preparation and committed after the removal — not
before, or the index would still list the files the removal deleted.

It had no `.gitignore`, so once it did have a repository, every generated
artifact and every linked module landed in the index and the inventory refused
the fixture for tracked and generated paths colliding. It carries the ignore
rules now, and therefore records the same tracked set as the repository it was
copied from.

Each removal script kept its own copy-target list and they had drifted: the
reference-feature fixture omitted `playwright.capabilities.config.ts`, which the
inventory requires. There is one list now. It also gained the install and
workspace identity — `.npmrc`, the lockfile, the workspace file — without which
the fixture is a different project, and the release evidence a candidate is
assembled from, without which no candidate can be built at all.

A tracked root the removal deletes is no longer required of the result: the
optional-recipe fixture deletes `recipes/`, and the inventory policy demanded
it back. Roots that are gone are pruned from the fixture's policy.

Two smaller causes. A platform integration file asserted the reference feature's
own route ids, so removing the feature left it importing a deleted module —
typecheck, the test run, coverage and the residue scan all failed on that one
misplaced assertion, which now lives in the feature's test tree. And the
canonical exact-count authority was re-imposed on a contract the fixture
deliberately reduces, failing the fixture for the reduction it exists to prove;
`CI_CONTRACT_MODE` already marked those runs and is now honoured by default.

The reference-feature fixture goes from failing before its first assertion to
1,612 passing with one failure, and that one is the live process-tree
observation test already red on the main tree.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 19:34:28 +09:00

2429 lines
106 KiB
TypeScript

import { spawn, spawnSync, type ChildProcess } from "node:child_process";
import { createHash, generateKeyPairSync, sign } from "node:crypto";
import { constants } 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";
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<string> {
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 }, '<testsuite name="one"/>\n'],
["report.html", { id: "html", kind: "html", maxBytes: 1_024 }, "<!doctype html><html></html>\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<void>, RegExp]
> = [
["missing", async (_root: string): Promise<void> => undefined, /not a regular file|ENOENT/i],
["empty", async (root: string): Promise<void> => { await writeArtifact(root, "report.json", ""); }, /size is outside/i],
["directory", async (root: string): Promise<void> => { await mkdir(path.join(root, "report.json")); }, /not a regular file/i],
["oversized", async (root: string): Promise<void> => { await writeArtifact(root, "report.json", "12345"); }, /size is outside/i],
["invalid UTF-8", async (root: string): Promise<void> => { await writeArtifact(root, "report.json", Buffer.from([0xc3, 0x28])); }, /encoded data was not valid|UTF-8/i],
["primitive JSON", async (root: string): Promise<void> => { await writeArtifact(root, "report.json", "1\n"); }, /object/i],
["array JSON", async (root: string): Promise<void> => { 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 }, "<testsuite>"],
["mismatched.xml", { id: "junit", kind: "junit", maxBytes: 1_024 }, "<testsuites><testsuite></testsuites></testsuite>"],
["trailing.xml", { id: "junit", kind: "junit", maxBytes: 1_024 }, "<testsuite/>garbage"],
["doctype.xml", { id: "junit", kind: "junit", maxBytes: 1_024 }, "<!DOCTYPE testsuite [<!ENTITY x SYSTEM 'file:///etc/passwd'>]><testsuite/>"],
["entity.xml", { id: "junit", kind: "junit", maxBytes: 1_024 }, "<testsuite name=\"&unapproved;\"/>"],
["numeric-entity.xml", { id: "junit", kind: "junit", maxBytes: 1_024 }, "<testsuite name=\"&#0;\"/>"],
["duplicate-root.xml", { id: "junit", kind: "junit", maxBytes: 1_024 }, "<testsuite/><testsuite/>"],
["broken.html", { id: "html", kind: "html", maxBytes: 1_024 }, "<!doctype html><html>"],
["mismatched.html", { id: "html", kind: "html", maxBytes: 1_024 }, "<!doctype html><html><body><main></body></main></html>"],
["duplicate-root.html", { id: "html", kind: "html", maxBytes: 1_024 }, "<!doctype html><html></html><html></html>"],
["invalid-root.html", { id: "html", kind: "html", maxBytes: 1_024 }, "<!doctype html><html:report></html:report>"],
["entity.html", { id: "html", kind: "html", maxBytes: 1_024 }, "<!DOCTYPE html [<!ENTITY x SYSTEM 'file:///etc/passwd'>]><html></html>"],
["trailing.html", { id: "html", kind: "html", maxBytes: 1_024 }, "<!doctype html><html></html>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 }, '<testsuite name="safe &amp; &#x41;"/>'],
["report.html", { id: "html", kind: "html", maxBytes: 4_096 }, '<!doctype html><html><body><script>const marker = "<fake>";</script></body></html>'],
["playwright.html", { id: "html", kind: "html", maxBytes: 4_096 }, '<!doctype html><html><body></body></html><template id="playwrightReportBase64">data:application/zip;base64,AA==</template>'],
] 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", `<testsuite>${"<group>".repeat(256)}${"</group>".repeat(256)}</testsuite>`],
["deep.html", "html", `<!doctype html><html>${"<div>".repeat(256)}${"</div>".repeat(256)}</html>`],
["many.xml", "junit", `<testsuite>${"<testcase/>".repeat(100_000)}</testsuite>`],
] 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 <path> [--extract-to <path>] [--github-output <path>]\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<string, any>;
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<string, unknown>;
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();
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 cgroupPids = await readCgroupPids(cgroupRoot);
const processArguments = cgroupPids.map((pid) => showProcessArguments(pid));
const directChildPids = await waitForDirectProviderChildren(execution.child.pid!);
const directChildArguments = directChildPids.map((pid) => showProcessArguments(pid));
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)
.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);
const reportIdentity = await lstat(fixture.reportPath);
const result = await execution.completion;
expect(result.code, result.stderr).toBe(0);
expect(result.stdout).not.toContain(credential);
expect(result.stderr).not.toContain(credential);
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([]);
}, 10_000);
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<ReturnType<typeof open>> | 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<ReturnType<typeof stageVerifiedPromotion>>;
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<typeof captureCiCandidateArchive>[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<string, any>) => void,
) => {
const files = { ...valid };
const value = JSON.parse(files[name].toString("utf8")) as Record<string, any>;
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<string, any>;
divergentReport.provider = "divergent-provider";
const reportMismatch = {
...valid,
"vulnerability-report.json": jsonBytes(divergentReport),
};
const absent = { ...valid } as Partial<typeof valid>;
delete absent["provider-verification.json"];
const jointlyRewritten = { ...valid };
const rewrittenVulnerability = JSON.parse(
jointlyRewritten["vulnerability-report.json"].toString("utf8"),
) as Record<string, any>;
rewrittenVulnerability.provider = "attacker-rewritten-provider";
jointlyRewritten["vulnerability-report.json"] = jsonBytes(rewrittenVulnerability);
const rewrittenProvider = JSON.parse(
jointlyRewritten["provider-verification.json"].toString("utf8"),
) as Record<string, any>;
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<string, any>;
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<string, any>;
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<ReturnType<typeof createPromotionStagingFixture>>) => {
fixture.input = { ...fixture.input, expectedArchiveSha256: "0".repeat(64) };
}, /archive SHA-256/u],
["report mutation", async (fixture: Awaited<ReturnType<typeof createPromotionStagingFixture>>) => {
await writeFile(fixture.input.vulnerabilityReportPath, "mutated report\n");
}, /invalid|provider/u],
["key rotation", async (fixture: Awaited<ReturnType<typeof createPromotionStagingFixture>>) => {
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<ReturnType<typeof createPromotionStagingFixture>>) => {
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<Record<string, string>>;
spawnTimeoutMs?: number;
maxBuffer?: number;
}>;
function runProviderSupervisor(
fixture: Awaited<ReturnType<typeof createProviderFixture>>,
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<ReturnType<typeof createProviderFixture>>,
input: ProviderSupervisorInput,
): Readonly<{
child: ChildProcess;
completion: Promise<Readonly<{
code: number | null;
signal: NodeJS.Signals | null;
stderr: string;
stdout: string;
}>>;
}> {
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<Readonly<{
code: number | null;
signal: NodeJS.Signals | null;
stderr: string;
stdout: string;
}>>((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<ReturnType<typeof createProviderFixture>>,
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<string> {
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<void> {
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<void> {
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<number[]> {
return (await readFile(path.join(cgroupRoot, "cgroup.procs"), "utf8"))
.trim().split("\n").filter(Boolean).map(Number);
}
async function waitForDirectProviderChildren(supervisorPid: number): Promise<number[]> {
for (let attempt = 0; attempt < 120; attempt += 1) {
const childrenPath = `/proc/${supervisorPid}/task/${supervisorPid}/children`;
let listing: string;
try {
listing = await readFile(childrenPath, "utf8");
} catch (error) {
if (hasErrorCode(error, "ENOENT")) {
throw new Error(
"provider supervisor exited before its children could be observed",
{ cause: error },
);
}
throw error;
}
const children = listing.trim().split(/\s+/u).filter(Boolean).map(Number);
const arguments_ = children.flatMap((pid) => {
try {
return [showProcessArguments(pid)];
} catch (error) {
if (!processExists(pid)) return [];
throw error;
}
});
if (
arguments_.some((value) => value.includes("/usr/bin/systemd-run")) &&
arguments_.some((value) => value.includes("provider-raw-guardian.ts"))
) {
return children;
}
await delay(25);
}
throw new Error("provider supervisor children were not simultaneously observable");
}
async function waitForDirectChildMatching(supervisorPid: number, pattern: string): Promise<number> {
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<void> {
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<void> {
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<void> {
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<string[]> {
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<Record<string, string>> {
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 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();
}
async function startLoopbackCanary(root: string): Promise<Readonly<{
child: ChildProcess;
marker: string;
port: number;
}>> {
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<void> {
if (child.exitCode !== null || child.signalCode !== null) return;
await new Promise<void>((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<ReturnType<typeof createProviderFixture>>,
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<Readonly<{ root: string; archivePath: string }>> {
const root = await temporaryRoot("ci-candidate-archive-");
const files = new Map<string, Buffer>();
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<ReturnType<typeof createReleaseCandidateManifest>>;
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<string> {
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<ReturnType<typeof createPromotionStagingFixture>>,
finalized: Awaited<ReturnType<typeof stageVerifiedPromotion>>,
) {
return {
runnerTempRoot: fixture.runnerTempRoot,
stagingRoot: finalized.stagingRoot,
cleanupToken: finalized.cleanupToken,
runnerTempIdentity: finalized.runnerTempIdentity,
stagingIdentity: finalized.stagingIdentity,
};
}