The cgroup test read the live process tree with one `ps` per pid and asserted while the provider was running. That was a race it used to win only because the sandbox was slow; now a whole run finishes in a few hundred milliseconds and `systemctl show` alone costs longer than the thing it describes. It records the tree from `/proc` every 5ms and asserts on the recording once the run is over, because the assertions were always about what the run contained. That restructuring immediately paid for itself: the supervisor had been failing to launch the sandbox at all, and the test was dying on the observation before it ever checked the exit code. It could not say why, because the supervisor consumed the child's output solely to enforce a byte cap and then discarded it — `exit=1` and nothing else. It now keeps the lines the sandbox tooling itself emits (`bwrap:`, `prlimit:`, `systemd-run:`, `systemctl:`), which cannot carry provider credentials because the provider command and its secrets travel in the args file. The failure now reads: sandboxed external provider failed: exit=1; sandbox reported: bwrap: loopback: Failed RTM_NEWADDR: Operation not permitted which is a host restriction — `kernel.apparmor_restrict_unprivileged_userns=1` — reproducible in two lines of shell containing none of this repository's code, and recorded in the ledger as such rather than carried as a product defect. Suites that spawn processes, build archives and sign evidence were given a 30s budget. The 10s default is sized for pure-JS unit tests; raising it globally would hide a genuinely hung test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1860 lines
69 KiB
TypeScript
1860 lines
69 KiB
TypeScript
import {
|
|
createHash,
|
|
generateKeyPairSync,
|
|
sign,
|
|
type KeyObject,
|
|
} from "node:crypto";
|
|
import { spawnSync } from "node:child_process";
|
|
import {
|
|
chmod,
|
|
lstat,
|
|
mkdir,
|
|
mkdtemp,
|
|
readFile,
|
|
readdir,
|
|
rename,
|
|
rm,
|
|
writeFile,
|
|
} from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import path from "node:path";
|
|
import { pathToFileURL } from "node:url";
|
|
import { EventEmitter } from "node:events";
|
|
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
import { localEvidenceAssessmentArtifactSchema } from "../../scripts/contracts/release-artifacts.ts";
|
|
import { PROMOTED_FILE_NAMES } from "../../scripts/contracts/promotion-artifacts.ts";
|
|
import { verifyArchivedLocalEvidence } from "../../scripts/lib/local-release-evidence.ts";
|
|
import {
|
|
evaluatePromotionEvidence,
|
|
providerEvidenceSignaturePayload,
|
|
providerPublicKeyFingerprint,
|
|
trustPolicySha256,
|
|
validateProviderEvidence,
|
|
} from "../../scripts/lib/provider-evidence.ts";
|
|
import { readProviderTrust } from "../../scripts/lib/provider-trust.ts";
|
|
import { superviseProviderEvidence } from "../../scripts/lib/provider-supervisor.ts";
|
|
import { runProviderProcess } from "../../scripts/lib/provider-process-runner.ts";
|
|
import { runStageVerifiedPromotionCli } from "../../scripts/lib/stage-verified-promotion-cli.ts";
|
|
import { publishPrivatePromotionStaging } from "../../scripts/lib/promotion-stager.ts";
|
|
import { verifyExactPromotionBundle } from "../../scripts/lib/exact-promotion-bundle.ts";
|
|
import {
|
|
LOCAL_EVIDENCE_ASSESSMENT_PATH,
|
|
LOCAL_EVIDENCE_VERIFIER_SOURCE_PATHS,
|
|
distSha256,
|
|
type ReleaseCandidateManifest,
|
|
} from "../../scripts/lib/release-candidate.ts";
|
|
import { supplyChainDigest } from "../../scripts/lib/supply-chain.ts";
|
|
|
|
/**
|
|
* This suite's budget, not the file's. The 10s default is sized for pure-JS
|
|
* unit tests; these spawn processes, build archives and sign evidence, and on a
|
|
* machine running the rest of the suite in parallel they legitimately need
|
|
* longer. Raising the global default instead would hide a genuinely hung test.
|
|
*/
|
|
const PROCESS_HEAVY_TIMEOUT_MS = 30_000;
|
|
|
|
const digest = (value: string): string =>
|
|
createHash("sha256").update(value).digest("hex");
|
|
const digestBytes = (value: Buffer): string =>
|
|
createHash("sha256").update(value).digest("hex");
|
|
|
|
function passingAssessment(): any {
|
|
return {
|
|
schemaVersion: 1 as const,
|
|
artifactType: "local-evidence-assessment" as const,
|
|
generatedAt: "2026-08-02T00:00:00.000Z",
|
|
status: "PASS" as const,
|
|
verifier: {
|
|
id: "clean-architecture-frontend-template/local-evidence-verifier",
|
|
version: "1",
|
|
sourceSha256: digest("verifier source"),
|
|
},
|
|
source: {
|
|
revision: "a".repeat(40),
|
|
sourceSetSha256: digest("source set"),
|
|
},
|
|
candidate: {
|
|
distSha256: digest("dist"),
|
|
lockfileSha256: digest("lockfile"),
|
|
sbomSha256: digest("sbom"),
|
|
},
|
|
secretScan: {
|
|
policySha256: digest("secret policy"),
|
|
sarifSha256: digest("secret sarif"),
|
|
scanInputSha256: digest("secret scan input"),
|
|
},
|
|
policyInputs: [
|
|
{
|
|
path: "config/security/dependency-policy.json",
|
|
bytes: 3,
|
|
sha256: digest("{}\n"),
|
|
},
|
|
],
|
|
evidenceInputs: [
|
|
{ path: "pnpm-lock.yaml", bytes: 9, sha256: digest("lockfile\n") },
|
|
],
|
|
checks: {
|
|
release: "PASS" as const,
|
|
supplyChain: "PASS" as const,
|
|
dependencyPolicy: "PASS" as const,
|
|
licensePolicy: "PASS" as const,
|
|
vulnerabilityPolicy: "PASS" as const,
|
|
secretScan: "PASS" as const,
|
|
},
|
|
failures: [] as string[],
|
|
};
|
|
}
|
|
|
|
describe("security follow-up contracts", () => {
|
|
it("rejects a PASS local assessment with a failed check or failure diagnostic", () => {
|
|
const failedCheck = passingAssessment();
|
|
failedCheck.checks.secretScan = "FAIL";
|
|
const failureDiagnostic = passingAssessment();
|
|
failureDiagnostic.failures.push("secret scan failed");
|
|
|
|
expect(localEvidenceAssessmentArtifactSchema.safeParse(failedCheck).success).toBe(false);
|
|
expect(localEvidenceAssessmentArtifactSchema.safeParse(failureDiagnostic).success).toBe(false);
|
|
expect(localEvidenceAssessmentArtifactSchema.parse(passingAssessment()).status).toBe("PASS");
|
|
});
|
|
|
|
it("rejects archived verification when independently required policy bytes are absent", async () => {
|
|
const fixture = await createArchivedAssessmentFixture();
|
|
try {
|
|
const result = await verifyArchivedLocalEvidence({
|
|
extractionRoot: fixture.root,
|
|
expectedManifest: fixture.manifest,
|
|
});
|
|
|
|
expect(result.status).toBe("FAIL");
|
|
expect(result.failures.join("\n")).toMatch(/archived local check|policy|missing/u);
|
|
} finally {
|
|
await rm(fixture.root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("binds the executable secret-scan rule source and rejects missing or changed archived bytes", async () => {
|
|
expect(LOCAL_EVIDENCE_VERIFIER_SOURCE_PATHS).toContain("scripts/lib/secret-scan.ts");
|
|
const fixture = await createArchivedAssessmentFixture();
|
|
const ruleSourcePath = path.join(fixture.root, "scripts/lib/secret-scan.ts");
|
|
try {
|
|
const missing = await verifyArchivedLocalEvidence({
|
|
extractionRoot: fixture.root,
|
|
expectedManifest: fixture.manifest,
|
|
});
|
|
expect(missing.failures).toContain(
|
|
"archived policy input is missing or invalid: scripts/lib/secret-scan.ts",
|
|
);
|
|
|
|
await mkdir(path.dirname(ruleSourcePath), { recursive: true });
|
|
await writeFile(ruleSourcePath, "export const secretScanRules = () => [];\n");
|
|
const changed = await verifyArchivedLocalEvidence({
|
|
extractionRoot: fixture.root,
|
|
expectedManifest: fixture.manifest,
|
|
});
|
|
expect(changed.failures).toContain(
|
|
"archived policy input binding mismatch: scripts/lib/secret-scan.ts",
|
|
);
|
|
} finally {
|
|
await rm(fixture.root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("keeps every direct runtime import of the archived local verifier in its source binding", async () => {
|
|
const verifierPath = path.join(process.cwd(), "scripts/lib/local-release-evidence.ts");
|
|
const source = await readFile(verifierPath, "utf8");
|
|
const directRuntimeSources = [
|
|
...source.matchAll(/\bfrom\s+"(\.{1,2}\/[^"\n]+\.ts)"/gu),
|
|
].map((match) =>
|
|
path
|
|
.relative(process.cwd(), path.resolve(path.dirname(verifierPath), match[1]!))
|
|
.replaceAll(path.sep, "/"),
|
|
);
|
|
expect(LOCAL_EVIDENCE_VERIFIER_SOURCE_PATHS).toEqual(
|
|
expect.arrayContaining(directRuntimeSources),
|
|
);
|
|
});
|
|
|
|
it("rejects a rehashed PASS assessment over contradictory archived subordinate FAIL evidence", async () => {
|
|
const fixture = await createArchivedAssessmentFixture();
|
|
try {
|
|
const supplyPath = "artifacts/security/supply-chain-verification.json";
|
|
const coherencePath = "artifacts/security/supply-chain-coherence.json";
|
|
const supply = JSON.parse(
|
|
await readFile(path.join(fixture.root, supplyPath), "utf8"),
|
|
) as Record<string, unknown>;
|
|
const coherence = {
|
|
schemaVersion: 1,
|
|
status: "FAIL",
|
|
dependencyCount: 0,
|
|
lockfileSha256: fixture.manifest.lockfileSha256,
|
|
distSha256: fixture.manifest.distSha256,
|
|
sbomSha256: digestBytes(
|
|
await readFile(path.join(fixture.root, "artifacts/release/sbom.cdx.json")),
|
|
),
|
|
failures: ["fixture subordinate failure"],
|
|
};
|
|
const changed = new Map<string, Buffer>([
|
|
[
|
|
supplyPath,
|
|
Buffer.from(
|
|
`${JSON.stringify({
|
|
...supply,
|
|
localStatus: "FAIL",
|
|
failures: ["fixture subordinate failure"],
|
|
})}\n`,
|
|
),
|
|
],
|
|
[coherencePath, Buffer.from(`${JSON.stringify(coherence)}\n`)],
|
|
]);
|
|
for (const [memberPath, bytes] of changed) {
|
|
await mkdir(path.dirname(path.join(fixture.root, memberPath)), { recursive: true });
|
|
await writeFile(path.join(fixture.root, memberPath), bytes);
|
|
}
|
|
|
|
const assessmentPath = path.join(fixture.root, LOCAL_EVIDENCE_ASSESSMENT_PATH);
|
|
const assessment = localEvidenceAssessmentArtifactSchema.parse(
|
|
JSON.parse(await readFile(assessmentPath, "utf8")) as unknown,
|
|
);
|
|
assessment.evidenceInputs = assessment.evidenceInputs
|
|
.filter(({ path: memberPath }) => memberPath !== coherencePath)
|
|
.map((row) => {
|
|
const bytes = changed.get(row.path);
|
|
return bytes
|
|
? { path: row.path, bytes: bytes.byteLength, sha256: digestBytes(bytes) }
|
|
: row;
|
|
});
|
|
const coherenceBytes = changed.get(coherencePath)!;
|
|
assessment.evidenceInputs.push({
|
|
path: coherencePath,
|
|
bytes: coherenceBytes.byteLength,
|
|
sha256: digestBytes(coherenceBytes),
|
|
});
|
|
assessment.evidenceInputs.sort((left, right) =>
|
|
left.path < right.path ? -1 : left.path > right.path ? 1 : 0,
|
|
);
|
|
const assessmentBytes = Buffer.from(`${JSON.stringify(assessment)}\n`);
|
|
await writeFile(assessmentPath, assessmentBytes);
|
|
|
|
const changedWithAssessment = new Map(changed);
|
|
changedWithAssessment.set(LOCAL_EVIDENCE_ASSESSMENT_PATH, assessmentBytes);
|
|
const files = fixture.manifest.files
|
|
.filter(({ path: memberPath }) => memberPath !== coherencePath)
|
|
.map((row) => {
|
|
const bytes = changedWithAssessment.get(row.path);
|
|
return bytes
|
|
? { path: row.path, bytes: bytes.byteLength, sha256: digestBytes(bytes) }
|
|
: row;
|
|
});
|
|
files.push({
|
|
path: coherencePath,
|
|
bytes: coherenceBytes.byteLength,
|
|
sha256: digestBytes(coherenceBytes),
|
|
});
|
|
files.sort((left, right) =>
|
|
left.path < right.path ? -1 : left.path > right.path ? 1 : 0,
|
|
);
|
|
const manifest = {
|
|
...fixture.manifest,
|
|
files,
|
|
bundleSha256: supplyChainDigest(files),
|
|
};
|
|
await writeFile(
|
|
path.join(fixture.root, "artifacts/release/release-candidate.json"),
|
|
`${JSON.stringify(manifest)}\n`,
|
|
);
|
|
|
|
const result = await verifyArchivedLocalEvidence({
|
|
extractionRoot: fixture.root,
|
|
expectedManifest: manifest,
|
|
});
|
|
expect(result.status).toBe("FAIL");
|
|
expect(result.failures.join("\n")).toMatch(/supply-chain.*not.*PASS|subordinate/u);
|
|
} finally {
|
|
await rm(fixture.root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("rejects archived verification when the assessment is absent", async () => {
|
|
const fixture = await createArchivedAssessmentFixture();
|
|
try {
|
|
await rm(path.join(fixture.root, LOCAL_EVIDENCE_ASSESSMENT_PATH));
|
|
const result = await verifyArchivedLocalEvidence({
|
|
extractionRoot: fixture.root,
|
|
expectedManifest: fixture.manifest,
|
|
});
|
|
expect(result.status).toBe("FAIL");
|
|
expect(result.failures).toContain("local evidence assessment is missing or invalid");
|
|
} finally {
|
|
await rm(fixture.root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("rejects digest-bound raw identity evidence that is not valid under its strict producer schema", async () => {
|
|
const fixture = await createArchivedAssessmentFixture();
|
|
try {
|
|
const provenancePath = "artifacts/release/provenance.json";
|
|
const malformed = Buffer.from(
|
|
`${JSON.stringify({ predicate: { materials: { sourceSetSha256: digest("source set") } } })}\n`,
|
|
);
|
|
await writeFile(path.join(fixture.root, provenancePath), malformed);
|
|
const assessmentPath = path.join(fixture.root, LOCAL_EVIDENCE_ASSESSMENT_PATH);
|
|
const assessment = localEvidenceAssessmentArtifactSchema.parse(
|
|
JSON.parse(await readFile(assessmentPath, "utf8")) as unknown,
|
|
);
|
|
assessment.evidenceInputs = assessment.evidenceInputs.map((row) =>
|
|
row.path === provenancePath
|
|
? { path: provenancePath, bytes: malformed.byteLength, sha256: digestBytes(malformed) }
|
|
: row,
|
|
);
|
|
const assessmentBytes = Buffer.from(`${JSON.stringify(assessment)}\n`);
|
|
await writeFile(assessmentPath, assessmentBytes);
|
|
const files = fixture.manifest.files.map((row) => {
|
|
if (row.path === provenancePath) {
|
|
return { path: provenancePath, bytes: malformed.byteLength, sha256: digestBytes(malformed) };
|
|
}
|
|
if (row.path === LOCAL_EVIDENCE_ASSESSMENT_PATH) {
|
|
return {
|
|
path: LOCAL_EVIDENCE_ASSESSMENT_PATH,
|
|
bytes: assessmentBytes.byteLength,
|
|
sha256: digestBytes(assessmentBytes),
|
|
};
|
|
}
|
|
return row;
|
|
});
|
|
const manifest = { ...fixture.manifest, files, bundleSha256: supplyChainDigest(files) };
|
|
await writeFile(
|
|
path.join(fixture.root, "artifacts/release/release-candidate.json"),
|
|
`${JSON.stringify(manifest)}\n`,
|
|
);
|
|
|
|
const result = await verifyArchivedLocalEvidence({
|
|
extractionRoot: fixture.root,
|
|
expectedManifest: manifest,
|
|
});
|
|
expect(result.status).toBe("FAIL");
|
|
expect(result.failures).toContain(
|
|
"archived source/build/provenance identities are missing or invalid",
|
|
);
|
|
} finally {
|
|
await rm(fixture.root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("accepts signed provider v2 evidence only for the exact run, source, archive, and nonce", () => {
|
|
const now = Date.parse("2026-08-02T01:00:00.000Z");
|
|
const vulnerabilityKeys = generateKeyPairSync("ed25519");
|
|
const provenanceKeys = generateKeyPairSync("ed25519");
|
|
const expected = providerExpectedContext();
|
|
const vulnerability = signedProviderV2(
|
|
{
|
|
source: expected.source,
|
|
candidate: expected.candidate,
|
|
schemaVersion: 2,
|
|
evidenceType: "vulnerability-report",
|
|
provider: "fixture-vulnerability",
|
|
issuedAt: "2026-08-02T01:00:00.000Z",
|
|
expiresAt: "2026-08-02T02:00:00.000Z",
|
|
run: { ...expected.run, invocationNonce: "1".repeat(64) },
|
|
secretScanAttestation: expected.secretScanAttestation,
|
|
findings: [],
|
|
},
|
|
"vulnerability-key",
|
|
vulnerabilityKeys.publicKey,
|
|
vulnerabilityKeys.privateKey,
|
|
);
|
|
const provenance = signedProviderV2(
|
|
{
|
|
source: expected.source,
|
|
candidate: expected.candidate,
|
|
schemaVersion: 2,
|
|
evidenceType: "provenance-attestation",
|
|
provider: "fixture-provenance",
|
|
signer: "fixture-workload",
|
|
issuedAt: "2026-08-02T01:00:00.000Z",
|
|
expiresAt: "2026-08-02T02:00:00.000Z",
|
|
run: { ...expected.run, invocationNonce: "2".repeat(64) },
|
|
subject: { name: "dist", digest: { sha256: expected.candidate.distSha256 } },
|
|
},
|
|
"provenance-key",
|
|
provenanceKeys.publicKey,
|
|
provenanceKeys.privateKey,
|
|
);
|
|
const result = evaluatePromotionEvidence({
|
|
expected: {
|
|
...expected,
|
|
vulnerabilityInvocationNonce: "1".repeat(64),
|
|
provenanceInvocationNonce: "2".repeat(64),
|
|
},
|
|
localStatus: "PASS",
|
|
vulnerabilityReport: vulnerability,
|
|
provenanceAttestation: provenance,
|
|
vulnerabilityTrust: trust("vulnerability-key", vulnerabilityKeys.publicKey),
|
|
provenanceTrust: trust("provenance-key", provenanceKeys.publicKey),
|
|
nowEpochMs: () => now,
|
|
});
|
|
|
|
expect(result).toEqual({
|
|
status: "PASS",
|
|
vulnerabilityStatus: "PASS",
|
|
provenanceAttestationStatus: "PASS",
|
|
failures: [],
|
|
});
|
|
const replayed = evaluatePromotionEvidence({
|
|
expected: {
|
|
...expected,
|
|
run: { id: expected.run.id, attempt: 2 },
|
|
vulnerabilityInvocationNonce: "1".repeat(64),
|
|
provenanceInvocationNonce: "2".repeat(64),
|
|
},
|
|
localStatus: "PASS",
|
|
vulnerabilityReport: vulnerability,
|
|
provenanceAttestation: provenance,
|
|
vulnerabilityTrust: trust("vulnerability-key", vulnerabilityKeys.publicKey),
|
|
provenanceTrust: trust("provenance-key", provenanceKeys.publicKey),
|
|
nowEpochMs: () => now,
|
|
});
|
|
expect(replayed.status).toBe("FAIL_UNVERIFIED");
|
|
expect(replayed.failures).toEqual(
|
|
expect.arrayContaining([
|
|
"vulnerability report run identity mismatch",
|
|
"provenance attestation run identity mismatch",
|
|
]),
|
|
);
|
|
});
|
|
|
|
it("rejects a signed vulnerability PASS when the captured SARIF attestation differs", () => {
|
|
const keys = generateKeyPairSync("ed25519");
|
|
const expected = providerExpectedContext();
|
|
const secretScanAttestation = {
|
|
status: "PASS" as const,
|
|
localEvidenceAssessmentSha256: digest("assessment"),
|
|
sourceSetSha256: expected.source.sourceSetSha256,
|
|
policySha256: digest("secret policy"),
|
|
sarifSha256: digest("real sarif"),
|
|
scanInputSha256: digest("scan input"),
|
|
};
|
|
const report = signedProviderV2(
|
|
{
|
|
schemaVersion: 2,
|
|
evidenceType: "vulnerability-report",
|
|
provider: "fixture-vulnerability",
|
|
issuedAt: "2026-08-02T01:00:00.000Z",
|
|
expiresAt: "2026-08-02T02:00:00.000Z",
|
|
run: { ...expected.run, invocationNonce: "1".repeat(64) },
|
|
source: expected.source,
|
|
candidate: expected.candidate,
|
|
secretScanAttestation,
|
|
findings: [],
|
|
},
|
|
"vulnerability-key",
|
|
keys.publicKey,
|
|
keys.privateKey,
|
|
);
|
|
const validated = validateProviderEvidence({
|
|
kind: "vulnerability",
|
|
value: report,
|
|
expected: {
|
|
...expected,
|
|
vulnerabilityInvocationNonce: "1".repeat(64),
|
|
provenanceInvocationNonce: "2".repeat(64),
|
|
secretScanAttestation: {
|
|
...secretScanAttestation,
|
|
sarifSha256: digest("forged empty sarif"),
|
|
},
|
|
},
|
|
trust: trust("vulnerability-key", keys.publicKey),
|
|
nowEpochMs: () => Date.parse("2026-08-02T01:00:00.000Z"),
|
|
});
|
|
expect(validated.status).toBe("FAIL_UNVERIFIED");
|
|
expect(validated.failures).toContain(
|
|
"vulnerability report secret scan attestation mismatch",
|
|
);
|
|
const forged = structuredClone(report);
|
|
forged.secretScanAttestation.sarifSha256 = digest("forged empty sarif");
|
|
const forgedValidation = validateProviderEvidence({
|
|
kind: "vulnerability",
|
|
value: forged,
|
|
expected: {
|
|
...expected,
|
|
vulnerabilityInvocationNonce: "1".repeat(64),
|
|
provenanceInvocationNonce: "2".repeat(64),
|
|
secretScanAttestation: forged.secretScanAttestation,
|
|
},
|
|
trust: trust("vulnerability-key", keys.publicKey),
|
|
nowEpochMs: () => Date.parse("2026-08-02T01:00:00.000Z"),
|
|
});
|
|
expect(forgedValidation.failures).toContain(
|
|
"vulnerability report signature verification failed",
|
|
);
|
|
|
|
const provenanceKeys = generateKeyPairSync("ed25519");
|
|
const provenance = signedProviderV2(
|
|
{
|
|
schemaVersion: 2,
|
|
evidenceType: "provenance-attestation",
|
|
provider: "fixture-provenance",
|
|
signer: "fixture-workload",
|
|
issuedAt: "2026-08-02T01:00:00.000Z",
|
|
expiresAt: "2026-08-02T02:00:00.000Z",
|
|
run: { ...expected.run, invocationNonce: "2".repeat(64) },
|
|
source: expected.source,
|
|
candidate: expected.candidate,
|
|
subject: { name: "dist", digest: { sha256: expected.candidate.distSha256 } },
|
|
},
|
|
"provenance-key",
|
|
provenanceKeys.publicKey,
|
|
provenanceKeys.privateKey,
|
|
);
|
|
const evaluated = evaluatePromotionEvidence({
|
|
expected: {
|
|
...expected,
|
|
vulnerabilityInvocationNonce: "1".repeat(64),
|
|
provenanceInvocationNonce: "2".repeat(64),
|
|
secretScanAttestation: {
|
|
...secretScanAttestation,
|
|
sarifSha256: digest("forged empty sarif"),
|
|
},
|
|
},
|
|
localStatus: "PASS",
|
|
vulnerabilityReport: report,
|
|
provenanceAttestation: provenance,
|
|
vulnerabilityTrust: trust("vulnerability-key", keys.publicKey),
|
|
provenanceTrust: trust("provenance-key", provenanceKeys.publicKey),
|
|
nowEpochMs: () => Date.parse("2026-08-02T01:00:00.000Z"),
|
|
});
|
|
expect(evaluated.vulnerabilityStatus).toBe("FAIL_UNVERIFIED");
|
|
expect(evaluated.failures).toContain(
|
|
"vulnerability report secret scan attestation mismatch",
|
|
);
|
|
});
|
|
|
|
it.each(["vulnerability", "provenance"] as const)(
|
|
"rejects correctly re-signed %s v2 context/time/replay drift",
|
|
(kind) => {
|
|
const now = Date.parse("2026-08-02T01:00:00.000Z");
|
|
const vulnerabilityKeys = generateKeyPairSync("ed25519");
|
|
const provenanceKeys = generateKeyPairSync("ed25519");
|
|
const expected = providerExpectedContext();
|
|
const baseVulnerability = providerUnsigned("vulnerability", expected);
|
|
const baseProvenance = providerUnsigned("provenance", expected);
|
|
const validVulnerability = signedProviderV2(
|
|
baseVulnerability,
|
|
"vulnerability-key",
|
|
vulnerabilityKeys.publicKey,
|
|
vulnerabilityKeys.privateKey,
|
|
);
|
|
const validProvenance = signedProviderV2(
|
|
baseProvenance,
|
|
"provenance-key",
|
|
provenanceKeys.publicKey,
|
|
provenanceKeys.privateKey,
|
|
);
|
|
const rawCases: Array<readonly [
|
|
string,
|
|
(value: Record<string, any>) => Record<string, any>,
|
|
RegExp,
|
|
]> = [
|
|
["schema v1", (value) => ({ ...value, schemaVersion: 1 }), /missing or invalid/u],
|
|
[
|
|
"evidence type",
|
|
(value) => ({
|
|
...value,
|
|
evidenceType:
|
|
kind === "vulnerability"
|
|
? "provenance-attestation"
|
|
: "vulnerability-report",
|
|
}),
|
|
/missing or invalid/u,
|
|
],
|
|
...(["archiveSha256", "bundleSha256", "distSha256", "lockfileSha256"] as const).map(
|
|
(field) => [
|
|
`candidate ${field}`,
|
|
(value: Record<string, any>) => ({
|
|
...value,
|
|
candidate: { ...value.candidate, [field]: "f".repeat(64) },
|
|
...(kind === "provenance" && field === "distSha256"
|
|
? {
|
|
subject: {
|
|
name: "dist",
|
|
digest: { sha256: "f".repeat(64) },
|
|
},
|
|
}
|
|
: {}),
|
|
}),
|
|
/candidate identity|subject dist/u,
|
|
] as const,
|
|
),
|
|
[
|
|
"different archive with same dist and lockfile",
|
|
(value) => ({
|
|
...value,
|
|
candidate: { ...value.candidate, archiveSha256: "e".repeat(64) },
|
|
}),
|
|
/candidate identity/u,
|
|
],
|
|
[
|
|
"source revision",
|
|
(value) => ({ ...value, source: { ...value.source, revision: "c".repeat(40) } }),
|
|
/source identity/u,
|
|
],
|
|
[
|
|
"source set",
|
|
(value) => ({ ...value, source: { ...value.source, sourceSetSha256: "c".repeat(64) } }),
|
|
/source identity/u,
|
|
],
|
|
[
|
|
"run id",
|
|
(value) => ({ ...value, run: { ...value.run, id: "other-run" } }),
|
|
/run identity/u,
|
|
],
|
|
[
|
|
"run attempt replay",
|
|
(value) => ({ ...value, run: { ...value.run, attempt: 2 } }),
|
|
/run identity/u,
|
|
],
|
|
[
|
|
"different nonce",
|
|
(value) => ({ ...value, run: { ...value.run, invocationNonce: "3".repeat(64) } }),
|
|
/invocation nonce/u,
|
|
],
|
|
[
|
|
"missing nonce",
|
|
(value) => {
|
|
const run = { ...value.run };
|
|
delete run.invocationNonce;
|
|
return { ...value, run };
|
|
},
|
|
/missing or invalid/u,
|
|
],
|
|
[
|
|
"uppercase nonce",
|
|
(value) => ({ ...value, run: { ...value.run, invocationNonce: "A".repeat(64) } }),
|
|
/missing or invalid/u,
|
|
],
|
|
[
|
|
"short nonce",
|
|
(value) => ({ ...value, run: { ...value.run, invocationNonce: "1".repeat(62) } }),
|
|
/missing or invalid/u,
|
|
],
|
|
[
|
|
"issued future boundary",
|
|
(value) => ({ ...value, issuedAt: "2026-08-02T01:05:00.001Z" }),
|
|
/future skew/u,
|
|
],
|
|
[
|
|
"expiry equality",
|
|
(value) => ({ ...value, expiresAt: "2026-08-02T01:00:00.000Z" }),
|
|
/expired/u,
|
|
],
|
|
[
|
|
"expiry past",
|
|
(value) => ({ ...value, expiresAt: "2026-08-02T00:59:59.999Z" }),
|
|
/expired/u,
|
|
],
|
|
[
|
|
"zero lifetime",
|
|
(value) => ({
|
|
...value,
|
|
issuedAt: "2026-08-02T01:01:00.000Z",
|
|
expiresAt: "2026-08-02T01:01:00.000Z",
|
|
}),
|
|
/not positive/u,
|
|
],
|
|
[
|
|
"negative lifetime",
|
|
(value) => ({
|
|
...value,
|
|
issuedAt: "2026-08-02T01:02:00.000Z",
|
|
expiresAt: "2026-08-02T01:01:59.999Z",
|
|
}),
|
|
/not positive/u,
|
|
],
|
|
[
|
|
"lifetime above two hours",
|
|
(value) => ({
|
|
...value,
|
|
issuedAt: "2026-08-02T01:00:00.000Z",
|
|
expiresAt: "2026-08-02T03:00:00.001Z",
|
|
}),
|
|
/exceeds two hours/u,
|
|
],
|
|
[
|
|
"wrong fingerprint",
|
|
(value) => ({
|
|
...value,
|
|
signature: {
|
|
...value.signature,
|
|
publicKeyFingerprint: `sha256:${"d".repeat(64)}`,
|
|
},
|
|
}),
|
|
/trust identity/u,
|
|
],
|
|
];
|
|
const cases = rawCases.map(([name, mutate, failure]) => ({
|
|
name,
|
|
mutate,
|
|
failure,
|
|
}));
|
|
|
|
for (const testCase of cases) {
|
|
const base = kind === "vulnerability" ? baseVulnerability : baseProvenance;
|
|
const mutated = testCase.mutate(structuredClone(base));
|
|
const resigned = signedProviderV2(
|
|
mutated,
|
|
kind === "vulnerability" ? "vulnerability-key" : "provenance-key",
|
|
kind === "vulnerability" ? vulnerabilityKeys.publicKey : provenanceKeys.publicKey,
|
|
kind === "vulnerability" ? vulnerabilityKeys.privateKey : provenanceKeys.privateKey,
|
|
"signature" in mutated && mutated.signature?.publicKeyFingerprint
|
|
? mutated.signature.publicKeyFingerprint
|
|
: undefined,
|
|
);
|
|
const result = evaluatePromotionEvidence({
|
|
expected: {
|
|
...expected,
|
|
vulnerabilityInvocationNonce: "1".repeat(64),
|
|
provenanceInvocationNonce: "2".repeat(64),
|
|
},
|
|
localStatus: "PASS",
|
|
vulnerabilityReport:
|
|
kind === "vulnerability" ? resigned : validVulnerability,
|
|
provenanceAttestation:
|
|
kind === "provenance" ? resigned : validProvenance,
|
|
vulnerabilityTrust: trust("vulnerability-key", vulnerabilityKeys.publicKey),
|
|
provenanceTrust: trust("provenance-key", provenanceKeys.publicKey),
|
|
nowEpochMs: () => now,
|
|
});
|
|
expect(result.status, testCase.name).toBe("FAIL_UNVERIFIED");
|
|
expect(result.failures.join("\n"), testCase.name).toMatch(testCase.failure);
|
|
}
|
|
},
|
|
);
|
|
|
|
it("canonicalizes provider fingerprints from DER SPKI across PEM wrapping and rejects Ed448", async () => {
|
|
const root = await mkdtemp(path.join(tmpdir(), "provider-fingerprint-"));
|
|
try {
|
|
const ed25519 = generateKeyPairSync("ed25519").publicKey;
|
|
const pem = ed25519.export({ type: "spki", format: "pem" }).toString();
|
|
const body = pem.replace(/-----[^-]+-----|\s/gu, "");
|
|
const wrapped = (width: number) =>
|
|
`-----BEGIN PUBLIC KEY-----\n${body.match(new RegExp(`.{1,${width}}`, "gu"))!.join("\n")}\n-----END PUBLIC KEY-----\n`;
|
|
await writeFile(path.join(root, "a.pem"), wrapped(64));
|
|
await writeFile(path.join(root, "b.pem"), wrapped(32));
|
|
const first = await readProviderTrust(root, "a.pem", "fixture-key");
|
|
const second = await readProviderTrust(root, "b.pem", "fixture-key");
|
|
expect(first?.publicKeyFingerprint).toBe(providerPublicKeyFingerprint(ed25519));
|
|
expect(second?.publicKeyFingerprint).toBe(first?.publicKeyFingerprint);
|
|
|
|
const ed448 = generateKeyPairSync("ed448").publicKey;
|
|
await writeFile(root + "/ed448.pem", ed448.export({ type: "spki", format: "pem" }));
|
|
await expect(readProviderTrust(root, "ed448.pem", "fixture-key")).resolves.toBeNull();
|
|
expect(() => providerPublicKeyFingerprint(ed448)).toThrow(/must be Ed25519/u);
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("captures the downloaded archive pathname exactly once in the provider supervisor", async () => {
|
|
const keys = generateKeyPairSync("ed25519");
|
|
const expected = providerExpectedContext();
|
|
let captureCount = 0;
|
|
let receivedEnvironment: Readonly<Record<string, string>> | undefined;
|
|
const manifest: ReleaseCandidateManifest = {
|
|
schemaVersion: 1,
|
|
distSha256: expected.candidate.distSha256,
|
|
lockfileSha256: expected.candidate.lockfileSha256,
|
|
bundleSha256: expected.candidate.bundleSha256,
|
|
files: [{ path: "pnpm-lock.yaml", bytes: 1, sha256: expected.candidate.lockfileSha256 }],
|
|
};
|
|
const result = await superviseProviderEvidence(
|
|
{
|
|
kind: "vulnerability",
|
|
archivePath: "/downloads/candidate.tar.gz",
|
|
expectedArchiveSha256: expected.candidate.archiveSha256,
|
|
expectedRun: {
|
|
id: expected.run.id,
|
|
attempt: expected.run.attempt,
|
|
sourceRevision: expected.source.revision,
|
|
},
|
|
trust: trust("vulnerability-key", keys.publicKey),
|
|
executeProvider: async ({ environment }) => {
|
|
receivedEnvironment = environment;
|
|
},
|
|
captureReport: async () => Buffer.from("{}\n"),
|
|
},
|
|
{
|
|
captureArchive: async (input) => {
|
|
captureCount += 1;
|
|
expect(input).toEqual({
|
|
archivePath: "/downloads/candidate.tar.gz",
|
|
expectedSha256: expected.candidate.archiveSha256,
|
|
});
|
|
return {
|
|
bytes: Buffer.from("captured archive"),
|
|
archiveSha256: expected.candidate.archiveSha256,
|
|
};
|
|
},
|
|
withVerifiedCandidate: (async (input: any) =>
|
|
input.verify({ extractionRoot: "/captured/extraction", manifest })) as any,
|
|
verifyLocalEvidence: async () => ({
|
|
status: "PASS",
|
|
identity: {
|
|
sourceRevision: expected.source.revision,
|
|
sourceSetSha256: expected.source.sourceSetSha256,
|
|
assessmentSha256: digest("assessment"),
|
|
secretScan: {
|
|
policySha256: digest("provider secret policy"),
|
|
sarifSha256: digest("provider secret sarif"),
|
|
scanInputSha256: digest("provider secret input"),
|
|
},
|
|
},
|
|
failures: [],
|
|
}),
|
|
validateUpload: (async (input: any) => {
|
|
expect("archivePath" in input).toBe(false);
|
|
return { sealed: true };
|
|
}) as any,
|
|
randomBytes: () => Buffer.alloc(32, 0x11),
|
|
nowEpochMs: () => Date.parse("2026-08-02T01:00:00.000Z"),
|
|
},
|
|
);
|
|
|
|
expect(captureCount).toBe(1);
|
|
expect(receivedEnvironment).toEqual(
|
|
expect.objectContaining({
|
|
PROVIDER_EVIDENCE_SCHEMA_VERSION: "2",
|
|
PROVIDER_INVOCATION_NONCE: "11".repeat(32),
|
|
PROVIDER_ISSUED_AT: "2026-08-02T01:00:00.000Z",
|
|
PROVIDER_EXPIRES_AT: "2026-08-02T02:00:00.000Z",
|
|
CI_RUN_ID: expected.run.id,
|
|
CI_RUN_ATTEMPT: "1",
|
|
SOURCE_REVISION: expected.source.revision,
|
|
CANDIDATE_ARCHIVE_SHA256: expected.candidate.archiveSha256,
|
|
}),
|
|
);
|
|
expect(result.evidence).toEqual({ sealed: true });
|
|
});
|
|
|
|
it("samples provider freshness after report capture instead of reusing issuance time", async () => {
|
|
const keys = generateKeyPairSync("ed25519");
|
|
const expected = providerExpectedContext();
|
|
const manifest: ReleaseCandidateManifest = {
|
|
schemaVersion: 1,
|
|
distSha256: expected.candidate.distSha256,
|
|
lockfileSha256: expected.candidate.lockfileSha256,
|
|
bundleSha256: expected.candidate.bundleSha256,
|
|
files: [{ path: "pnpm-lock.yaml", bytes: 1, sha256: expected.candidate.lockfileSha256 }],
|
|
};
|
|
const issuedSample = Date.parse("2026-08-02T01:00:00.000Z");
|
|
const validationSample = Date.parse("2026-08-02T02:00:00.001Z");
|
|
const samples = [issuedSample, validationSample];
|
|
let issuedAt = "";
|
|
await expect(
|
|
superviseProviderEvidence(
|
|
{
|
|
kind: "vulnerability",
|
|
archivePath: "/downloads/candidate.tar.gz",
|
|
expectedArchiveSha256: expected.candidate.archiveSha256,
|
|
expectedRun: {
|
|
id: expected.run.id,
|
|
attempt: expected.run.attempt,
|
|
sourceRevision: expected.source.revision,
|
|
},
|
|
trust: trust("vulnerability-key", keys.publicKey),
|
|
executeProvider: async ({ environment }) => {
|
|
issuedAt = environment.PROVIDER_ISSUED_AT!;
|
|
},
|
|
captureReport: async () => Buffer.from("{}\n"),
|
|
},
|
|
{
|
|
captureArchive: async () => ({
|
|
bytes: Buffer.from("captured archive"),
|
|
archiveSha256: expected.candidate.archiveSha256,
|
|
}),
|
|
withVerifiedCandidate: (async (input: any) =>
|
|
input.verify({ extractionRoot: "/captured/extraction", manifest })) as any,
|
|
verifyLocalEvidence: async () => ({
|
|
status: "PASS",
|
|
identity: {
|
|
sourceRevision: expected.source.revision,
|
|
sourceSetSha256: expected.source.sourceSetSha256,
|
|
assessmentSha256: digest("assessment"),
|
|
secretScan: {
|
|
policySha256: digest("provider secret policy"),
|
|
sarifSha256: digest("provider secret sarif"),
|
|
scanInputSha256: digest("provider secret input"),
|
|
},
|
|
},
|
|
failures: [],
|
|
}),
|
|
validateUpload: (async (input: any) => {
|
|
expect(input.nowEpochMs()).toBe(validationSample);
|
|
throw new Error("provider report expired during execution");
|
|
}) as any,
|
|
randomBytes: () => Buffer.alloc(32, 0x33),
|
|
nowEpochMs: () => samples.shift()!,
|
|
},
|
|
),
|
|
).rejects.toThrow(/expired during execution/u);
|
|
expect(issuedAt).toBe("2026-08-02T01:00:00.000Z");
|
|
});
|
|
|
|
it("kills a timed-out provider but settles only after the child closes", async () => {
|
|
const child = new EventEmitter() as EventEmitter & {
|
|
kill(signal: NodeJS.Signals): boolean;
|
|
};
|
|
let killedWith: NodeJS.Signals | undefined;
|
|
child.kill = (signal) => {
|
|
killedWith = signal;
|
|
return true;
|
|
};
|
|
let fireTimeout: (() => void) | undefined;
|
|
let settled = false;
|
|
const running = runProviderProcess(
|
|
{ executable: "/usr/bin/bwrap", arguments: [], environment: {}, timeoutMs: 1 },
|
|
{
|
|
spawnChild: () => child as any,
|
|
setTimer: (callback) => {
|
|
fireTimeout = callback;
|
|
return 1 as any;
|
|
},
|
|
clearTimer: () => undefined,
|
|
},
|
|
).finally(() => {
|
|
settled = true;
|
|
});
|
|
fireTimeout?.();
|
|
await Promise.resolve();
|
|
expect(killedWith).toBe("SIGKILL");
|
|
expect(settled).toBe(false);
|
|
child.emit("close", null, "SIGKILL");
|
|
await expect(running).rejects.toThrow(/timed out/u);
|
|
expect(settled).toBe(true);
|
|
});
|
|
|
|
it("captures process-group kill errors, attempts child fallback, and settles after close", async () => {
|
|
const child = new EventEmitter() as EventEmitter & {
|
|
pid: number;
|
|
kill(signal: NodeJS.Signals): boolean;
|
|
};
|
|
child.pid = 12_346;
|
|
let fallbackSignal: NodeJS.Signals | undefined;
|
|
child.kill = (signal) => {
|
|
fallbackSignal = signal;
|
|
return true;
|
|
};
|
|
let fireTimeout: (() => void) | undefined;
|
|
const running = runProviderProcess(
|
|
{ executable: "/usr/bin/bwrap", arguments: [], environment: {}, timeoutMs: 1 },
|
|
{
|
|
spawnChild: () => child as any,
|
|
setTimer: (callback) => {
|
|
fireTimeout = callback;
|
|
return 1 as any;
|
|
},
|
|
clearTimer: () => undefined,
|
|
killProcessGroup: () => {
|
|
throw Object.assign(new Error("group kill denied"), { code: "EPERM" });
|
|
},
|
|
},
|
|
);
|
|
expect(() => fireTimeout?.()).not.toThrow();
|
|
expect(fallbackSignal).toBe("SIGKILL");
|
|
child.emit("close", null, "SIGKILL");
|
|
await expect(running).rejects.toThrow(/timed out.*kill failed.*close/u);
|
|
});
|
|
|
|
it("kills and reaps a stubborn provider process group including its descendant", async () => {
|
|
const root = await mkdtemp(path.join(tmpdir(), "provider-process-group-"));
|
|
const descendantPidPath = path.join(root, "descendant.pid");
|
|
try {
|
|
const source = [
|
|
"const { spawn } = require('node:child_process');",
|
|
"const { writeFileSync } = require('node:fs');",
|
|
"const child = spawn(process.execPath, ['-e', `process.on('SIGTERM', () => {}); setInterval(() => {}, 1000)`], { stdio: 'ignore' });",
|
|
"writeFileSync(process.env.DESCENDANT_PID_PATH, String(child.pid));",
|
|
"process.on('SIGTERM', () => {});",
|
|
"setInterval(() => {}, 1000);",
|
|
].join("\n");
|
|
const running = runProviderProcess({
|
|
executable: process.execPath,
|
|
arguments: ["-e", source],
|
|
environment: {
|
|
PATH: process.env.PATH,
|
|
DESCENDANT_PID_PATH: descendantPidPath,
|
|
},
|
|
timeoutMs: 250,
|
|
});
|
|
await expect(running).rejects.toThrow(/timed out.*process close/u);
|
|
const descendantPid = Number(await readFile(descendantPidPath, "utf8"));
|
|
expect(Number.isSafeInteger(descendantPid) && descendantPid > 0).toBe(true);
|
|
expect(() => process.kill(descendantPid, 0)).toThrow(/ESRCH|no such process/u);
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it.each(["open failure", "partial write failure"])(
|
|
"cleans finalized staging from memory when GITHUB_OUTPUT has a %s",
|
|
async (failureKind) => {
|
|
const finalized = {
|
|
stagingRoot: "/runner/promotion-run-1-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
|
cleanupToken: "promotion-run-1-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
|
runnerTempIdentity: { dev: 10, ino: 20 },
|
|
stagingIdentity: { dev: 30, ino: 40 },
|
|
files: [],
|
|
} as const;
|
|
let cleanupInput: unknown;
|
|
let appendCalls = 0;
|
|
const environment = {
|
|
CANDIDATE_ARCHIVE_PATH: "candidate.tar.gz",
|
|
CANDIDATE_ARCHIVE_SHA256: "a".repeat(64),
|
|
VULNERABILITY_REPORT_PATH: "vulnerability.json",
|
|
PROVENANCE_ATTESTATION_PATH: "provenance.json",
|
|
VULNERABILITY_PUBLIC_KEY_PATH: "vulnerability.pem",
|
|
VULNERABILITY_KEY_ID: "vulnerability-key",
|
|
PROVENANCE_PUBLIC_KEY_PATH: "provenance.pem",
|
|
PROVENANCE_KEY_ID: "provenance-key",
|
|
CI_RUN_ID: "run",
|
|
CI_RUN_ATTEMPT: "1",
|
|
VITE_COMMIT_SHA: "b".repeat(40),
|
|
VULNERABILITY_INVOCATION_NONCE: "c".repeat(64),
|
|
PROVENANCE_INVOCATION_NONCE: "d".repeat(64),
|
|
RUNNER_TEMP: "/runner",
|
|
GITHUB_OUTPUT: "/runner/github-output",
|
|
};
|
|
await expect(
|
|
runStageVerifiedPromotionCli(environment, {
|
|
cwd: () => "/workspace",
|
|
finalize: async () => finalized as any,
|
|
appendOutput: async () => {
|
|
appendCalls += 1;
|
|
if (failureKind === "partial write failure") {
|
|
// The output sink accepted an unspecified prefix before rejecting.
|
|
}
|
|
throw new Error(failureKind);
|
|
},
|
|
cleanup: async (input) => {
|
|
cleanupInput = input;
|
|
},
|
|
writeStdout: () => undefined,
|
|
}),
|
|
).rejects.toThrow(new RegExp(failureKind, "u"));
|
|
expect(appendCalls).toBe(1);
|
|
expect(cleanupInput).toEqual({
|
|
runnerTempRoot: "/runner",
|
|
stagingRoot: finalized.stagingRoot,
|
|
cleanupToken: finalized.cleanupToken,
|
|
runnerTempIdentity: finalized.runnerTempIdentity,
|
|
stagingIdentity: finalized.stagingIdentity,
|
|
});
|
|
},
|
|
);
|
|
|
|
it("forces exact private staging modes in an isolated child with umask 077", async () => {
|
|
const root = await mkdtemp(path.join(tmpdir(), "promotion-umask-"));
|
|
try {
|
|
const stagerUrl = pathToFileURL(
|
|
path.join(process.cwd(), "scripts/lib/promotion-stager.ts"),
|
|
).href;
|
|
const contractsUrl = pathToFileURL(
|
|
path.join(process.cwd(), "scripts/contracts/promotion-artifacts.ts"),
|
|
).href;
|
|
const childPath = path.join(root, "umask-child.mjs");
|
|
const resultPath = path.join(root, "result.json");
|
|
await writeFile(resultPath, "{}\n", { mode: 0o600 });
|
|
await writeFile(
|
|
childPath,
|
|
[
|
|
`import { lstat, writeFile } from "node:fs/promises";`,
|
|
`import path from "node:path";`,
|
|
`import { createHash } from "node:crypto";`,
|
|
`import { cleanupFinalizedPromotion, publishPrivatePromotionStaging } from ${JSON.stringify(stagerUrl)};`,
|
|
`import { PROMOTED_FILE_NAMES } from ${JSON.stringify(contractsUrl)};`,
|
|
`process.umask(Number.parseInt(process.argv[2], 8));`,
|
|
`const runnerTempRoot = process.argv[3];`,
|
|
`const files = PROMOTED_FILE_NAMES.map((name) => { const bytes = Buffer.from(name); return { name, bytes, sha256: createHash("sha256").update(bytes).digest("hex") }; });`,
|
|
`const finalized = await publishPrivatePromotionStaging(runnerTempRoot, { id: "umask", attempt: 1 }, files, () => Buffer.alloc(16, 1));`,
|
|
`const directoryMode = (await lstat(finalized.stagingRoot)).mode & 0o777;`,
|
|
`const fileModes = await Promise.all(PROMOTED_FILE_NAMES.map(async (name) => (await lstat(path.join(finalized.stagingRoot, name))).mode & 0o777));`,
|
|
`await cleanupFinalizedPromotion({ runnerTempRoot, stagingRoot: finalized.stagingRoot, cleanupToken: finalized.cleanupToken, runnerTempIdentity: finalized.runnerTempIdentity, stagingIdentity: finalized.stagingIdentity });`,
|
|
`await writeFile(process.argv[4], JSON.stringify({ directoryMode, fileModes }));`,
|
|
].join("\n"),
|
|
);
|
|
const child = spawnSync(process.execPath, [childPath, "077", root, resultPath], {
|
|
cwd: root,
|
|
encoding: "utf8",
|
|
timeout: 30_000,
|
|
});
|
|
expect(child.status, `${child.stdout}\n${child.stderr}`).toBe(0);
|
|
expect(JSON.parse(await readFile(resultPath, "utf8"))).toEqual({
|
|
directoryMode: 0o700,
|
|
fileModes: [0o400, 0o400, 0o400, 0o400, 0o400],
|
|
});
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("rejects a staged file unlinked and recreated after its original write", async () => {
|
|
const root = await mkdtemp(path.join(tmpdir(), "promotion-seal-recreate-"));
|
|
const files = privatePromotionFiles();
|
|
const token = `promotion-seal-1-${"11".repeat(16)}`;
|
|
try {
|
|
await expect(
|
|
publishPrivatePromotionStaging(
|
|
root,
|
|
{ id: "seal", attempt: 1 },
|
|
files,
|
|
() => Buffer.alloc(16, 0x11),
|
|
undefined,
|
|
async (name) => {
|
|
if (name !== PROMOTED_FILE_NAMES.at(-1)) return;
|
|
const first = path.join(root, token, PROMOTED_FILE_NAMES[0]);
|
|
await rm(first);
|
|
await writeFile(first, "replacement bytes\n", { mode: 0o400 });
|
|
},
|
|
),
|
|
).rejects.toThrow(/staged.*digest|inode|seal/u);
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("rejects staged mode drift before returning the upload root", async () => {
|
|
const root = await mkdtemp(path.join(tmpdir(), "promotion-seal-mode-"));
|
|
const files = privatePromotionFiles();
|
|
const token = `promotion-seal-1-${"12".repeat(16)}`;
|
|
try {
|
|
await expect(
|
|
publishPrivatePromotionStaging(
|
|
root,
|
|
{ id: "seal", attempt: 1 },
|
|
files,
|
|
() => Buffer.alloc(16, 0x12),
|
|
undefined,
|
|
async (name) => {
|
|
if (name === PROMOTED_FILE_NAMES.at(-1)) {
|
|
await chmod(path.join(root, token, PROMOTED_FILE_NAMES[0]), 0o600);
|
|
}
|
|
},
|
|
),
|
|
).rejects.toThrow(/mode.*0400|staged.*mode|seal/u);
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("rejects staging leaf replacement between mkdir and descriptor open", async () => {
|
|
const root = await mkdtemp(path.join(tmpdir(), "promotion-preopen-replace-"));
|
|
const files = privatePromotionFiles();
|
|
const token = `promotion-preopen-1-${"13".repeat(16)}`;
|
|
const displaced = path.join(root, `${token}-displaced`);
|
|
const replacementCanary = path.join(root, token, PROMOTED_FILE_NAMES[0]);
|
|
try {
|
|
await expect(
|
|
publishPrivatePromotionStaging(
|
|
root,
|
|
{ id: "preopen", attempt: 1 },
|
|
files,
|
|
() => Buffer.alloc(16, 0x13),
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
async (stagingRoot) => {
|
|
await rename(stagingRoot, displaced);
|
|
await mkdir(stagingRoot, { mode: 0o700 });
|
|
await writeFile(replacementCanary, "external replacement canary\n");
|
|
},
|
|
),
|
|
).rejects.toThrow(/staging leaf.*changed|mkdir.*open|identity/u);
|
|
await expect(readFile(replacementCanary, "utf8")).resolves.toBe(
|
|
"external replacement canary\n",
|
|
);
|
|
await expect(readdir(displaced)).resolves.toEqual([]);
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("does not scan a crowded parent to recover an unverified pre-open leaf", async () => {
|
|
const root = await mkdtemp(path.join(tmpdir(), "promotion-preopen-bounded-"));
|
|
const files = privatePromotionFiles();
|
|
const token = `promotion-preopen-bound-1-${"14".repeat(16)}`;
|
|
const displaced = path.join(root, `${token}-displaced`);
|
|
const replacementCanary = path.join(root, token, PROMOTED_FILE_NAMES[0]);
|
|
try {
|
|
for (let offset = 0; offset < 4_097; offset += 128) {
|
|
await Promise.all(
|
|
Array.from(
|
|
{ length: Math.min(128, 4_097 - offset) },
|
|
(_, index) =>
|
|
mkdir(
|
|
path.join(
|
|
root,
|
|
`noise-${String(offset + index).padStart(4, "0")}`,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
let failure: unknown;
|
|
try {
|
|
await publishPrivatePromotionStaging(
|
|
root,
|
|
{ id: "preopen-bound", attempt: 1 },
|
|
files,
|
|
() => Buffer.alloc(16, 0x14),
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
async (stagingRoot) => {
|
|
await rename(stagingRoot, displaced);
|
|
await mkdir(stagingRoot, { mode: 0o700 });
|
|
await writeFile(replacementCanary, "external replacement canary\n");
|
|
},
|
|
);
|
|
} catch (error) {
|
|
failure = error;
|
|
}
|
|
expect(failure).toBeInstanceOf(Error);
|
|
expect(failure).not.toBeInstanceOf(AggregateError);
|
|
expect((failure as Error).message).toMatch(/staging leaf.*identity/i);
|
|
await expect(readdir(displaced)).resolves.toEqual([]);
|
|
await expect(readFile(replacementCanary, "utf8")).resolves.toBe(
|
|
"external replacement canary\n",
|
|
);
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
}, 20_000);
|
|
|
|
it("leaves a non-empty moved original untouched after pre-open mismatch", async () => {
|
|
const root = await mkdtemp(path.join(tmpdir(), "promotion-preopen-nonempty-"));
|
|
const files = privatePromotionFiles();
|
|
const token = `promotion-preopen-nonempty-1-${"15".repeat(16)}`;
|
|
const displaced = path.join(root, `${token}-displaced`);
|
|
const ownedResidual = path.join(displaced, "owned-residual");
|
|
const replacementCanary = path.join(root, token, PROMOTED_FILE_NAMES[0]);
|
|
try {
|
|
let failure: unknown;
|
|
try {
|
|
await publishPrivatePromotionStaging(
|
|
root,
|
|
{ id: "preopen-nonempty", attempt: 1 },
|
|
files,
|
|
() => Buffer.alloc(16, 0x15),
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
async (stagingRoot) => {
|
|
await rename(stagingRoot, displaced);
|
|
await writeFile(ownedResidual, "owned residual\n");
|
|
await mkdir(stagingRoot, { mode: 0o700 });
|
|
await writeFile(replacementCanary, "external replacement canary\n");
|
|
},
|
|
);
|
|
} catch (error) {
|
|
failure = error;
|
|
}
|
|
expect(failure).toBeInstanceOf(Error);
|
|
expect(failure).not.toBeInstanceOf(AggregateError);
|
|
expect((failure as Error).message).toMatch(/staging leaf.*identity/i);
|
|
await expect(readFile(ownedResidual, "utf8")).resolves.toBe(
|
|
"owned residual\n",
|
|
);
|
|
await expect(readFile(replacementCanary, "utf8")).resolves.toBe(
|
|
"external replacement canary\n",
|
|
);
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("does not search outside the parent for a moved unverified original", async () => {
|
|
const root = await mkdtemp(path.join(tmpdir(), "promotion-preopen-missing-"));
|
|
const outside = await mkdtemp(path.join(tmpdir(), "promotion-preopen-moved-"));
|
|
const files = privatePromotionFiles();
|
|
const token = `promotion-preopen-missing-1-${"16".repeat(16)}`;
|
|
const displaced = path.join(outside, token);
|
|
const replacementCanary = path.join(root, token, PROMOTED_FILE_NAMES[0]);
|
|
try {
|
|
let failure: unknown;
|
|
try {
|
|
await publishPrivatePromotionStaging(
|
|
root,
|
|
{ id: "preopen-missing", attempt: 1 },
|
|
files,
|
|
() => Buffer.alloc(16, 0x16),
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
async (stagingRoot) => {
|
|
await rename(stagingRoot, displaced);
|
|
await mkdir(stagingRoot, { mode: 0o700 });
|
|
await writeFile(replacementCanary, "external replacement canary\n");
|
|
},
|
|
);
|
|
} catch (error) {
|
|
failure = error;
|
|
}
|
|
expect(failure).toBeInstanceOf(Error);
|
|
expect(failure).not.toBeInstanceOf(AggregateError);
|
|
expect((failure as Error).message).toMatch(/staging leaf.*identity/i);
|
|
await expect(lstat(displaced)).resolves.toEqual(
|
|
expect.objectContaining({ dev: expect.any(Number), ino: expect.any(Number) }),
|
|
);
|
|
await expect(readFile(replacementCanary, "utf8")).resolves.toBe(
|
|
"external replacement canary\n",
|
|
);
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
await rm(outside, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("rejects a fresh signed exact-five bundle replayed under a different expected run", async () => {
|
|
const fixture = syntheticSignedPromotionBundle();
|
|
await expect(
|
|
verifyExactPromotionBundle(fixture.files, {
|
|
...fixture.verification,
|
|
expected: {
|
|
...fixture.verification.expected,
|
|
run: { id: "different-run", attempt: 1 },
|
|
},
|
|
}),
|
|
).rejects.toThrow(/external expected run.*mismatch|expected promotion run/u);
|
|
});
|
|
|
|
it("requires every external expected identity variable at the exact promotion CLI", async () => {
|
|
const fixture = syntheticSignedPromotionBundle();
|
|
const root = await mkdtemp(path.join(tmpdir(), "promotion-replay-cli-"));
|
|
const bundleRoot = path.join(root, "bundle");
|
|
try {
|
|
await mkdir(bundleRoot);
|
|
for (const [name, bytes] of Object.entries(fixture.files)) {
|
|
await writeFile(path.join(bundleRoot, name), bytes);
|
|
}
|
|
await writeFile(path.join(root, "vulnerability.pem"), fixture.vulnerabilityPem);
|
|
await writeFile(path.join(root, "provenance.pem"), fixture.provenancePem);
|
|
const cliPath = path.join(process.cwd(), "scripts/verify-exact-promotion-bundle.ts");
|
|
const baseEnvironment: NodeJS.ProcessEnv = {
|
|
...process.env,
|
|
PROMOTION_BUNDLE_ROOT: bundleRoot,
|
|
VULNERABILITY_PUBLIC_KEY_PATH: "vulnerability.pem",
|
|
VULNERABILITY_KEY_ID: "synthetic-vulnerability",
|
|
PROVENANCE_PUBLIC_KEY_PATH: "provenance.pem",
|
|
PROVENANCE_KEY_ID: "synthetic-provenance",
|
|
EXPECTED_PROMOTION_RUN_ID: fixture.verification.expected.run.id,
|
|
EXPECTED_PROMOTION_RUN_ATTEMPT: String(
|
|
fixture.verification.expected.run.attempt,
|
|
),
|
|
EXPECTED_PROMOTION_SOURCE_REVISION:
|
|
fixture.verification.expected.sourceRevision,
|
|
EXPECTED_PROMOTION_ARCHIVE_SHA256:
|
|
fixture.verification.expected.archiveSha256,
|
|
};
|
|
const requiredExpected = [
|
|
"EXPECTED_PROMOTION_RUN_ID",
|
|
"EXPECTED_PROMOTION_RUN_ATTEMPT",
|
|
"EXPECTED_PROMOTION_SOURCE_REVISION",
|
|
"EXPECTED_PROMOTION_ARCHIVE_SHA256",
|
|
] as const;
|
|
for (const missing of requiredExpected) {
|
|
const environment = { ...baseEnvironment };
|
|
delete environment[missing];
|
|
const result = spawnSync(process.execPath, [cliPath], {
|
|
cwd: root,
|
|
encoding: "utf8",
|
|
env: environment,
|
|
});
|
|
expect(result.status, missing).not.toBe(0);
|
|
expect(result.stderr, missing).toContain(
|
|
`exact promotion verification environment is missing ${missing}`,
|
|
);
|
|
}
|
|
for (const [name, value, diagnostic] of [
|
|
["EXPECTED_PROMOTION_RUN_ID", "different-run", /external expected run.*mismatch/u],
|
|
["EXPECTED_PROMOTION_RUN_ATTEMPT", "2", /external expected run.*mismatch/u],
|
|
["EXPECTED_PROMOTION_SOURCE_REVISION", "f".repeat(40), /external expected source revision.*mismatch/u],
|
|
["EXPECTED_PROMOTION_ARCHIVE_SHA256", "0".repeat(64), /external expected archive digest.*mismatch/u],
|
|
] as const) {
|
|
const result = spawnSync(process.execPath, [cliPath], {
|
|
cwd: root,
|
|
encoding: "utf8",
|
|
env: { ...baseEnvironment, [name]: value },
|
|
});
|
|
expect(result.status, name).not.toBe(0);
|
|
expect(result.stderr, name).toMatch(diagnostic);
|
|
}
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true });
|
|
}
|
|
}, PROCESS_HEAVY_TIMEOUT_MS);
|
|
});
|
|
|
|
function providerExpectedContext() {
|
|
return {
|
|
run: { id: "run-42", attempt: 1 },
|
|
source: { revision: "b".repeat(40), sourceSetSha256: digest("provider source") },
|
|
candidate: {
|
|
archiveSha256: digest("archive"),
|
|
bundleSha256: digest("bundle"),
|
|
distSha256: digest("provider dist"),
|
|
lockfileSha256: digest("provider lockfile"),
|
|
},
|
|
secretScanAttestation: {
|
|
status: "PASS" as const,
|
|
localEvidenceAssessmentSha256: digest("provider assessment"),
|
|
sourceSetSha256: digest("provider source"),
|
|
policySha256: digest("provider secret policy"),
|
|
sarifSha256: digest("provider secret sarif"),
|
|
scanInputSha256: digest("provider secret input"),
|
|
},
|
|
} as const;
|
|
}
|
|
|
|
function privatePromotionFiles() {
|
|
return PROMOTED_FILE_NAMES.map((name) => {
|
|
const bytes = Buffer.from(`${name}\n`);
|
|
return { name, bytes, sha256: digestBytes(bytes) };
|
|
});
|
|
}
|
|
|
|
function syntheticSignedPromotionBundle() {
|
|
const vulnerabilityKeys = generateKeyPairSync("ed25519");
|
|
const provenanceKeys = generateKeyPairSync("ed25519");
|
|
const archiveBytes = Buffer.from("synthetic signed replay archive\n");
|
|
const run = { id: "signed-run", attempt: 1 } as const;
|
|
const source = {
|
|
revision: "a".repeat(40),
|
|
sourceSetSha256: digest("synthetic-source-set"),
|
|
} as const;
|
|
const candidate = {
|
|
archiveSha256: digestBytes(archiveBytes),
|
|
bundleSha256: digest("synthetic-bundle"),
|
|
distSha256: digest("synthetic-dist"),
|
|
lockfileSha256: digest("synthetic-lock"),
|
|
} as const;
|
|
const vulnerability = signedProviderV2(
|
|
{
|
|
schemaVersion: 2,
|
|
evidenceType: "vulnerability-report",
|
|
provider: "synthetic-vulnerability-provider",
|
|
issuedAt: "2026-08-02T01:00:00.000Z",
|
|
expiresAt: "2026-08-02T02:00:00.000Z",
|
|
run: { ...run, invocationNonce: "1".repeat(64) },
|
|
source,
|
|
candidate,
|
|
secretScanAttestation: {
|
|
status: "PASS",
|
|
localEvidenceAssessmentSha256: digest("synthetic-assessment"),
|
|
sourceSetSha256: source.sourceSetSha256,
|
|
policySha256: digest("synthetic-policy"),
|
|
sarifSha256: digest("synthetic-sarif"),
|
|
scanInputSha256: digest("synthetic-scan-input"),
|
|
},
|
|
findings: [],
|
|
},
|
|
"synthetic-vulnerability",
|
|
vulnerabilityKeys.publicKey,
|
|
vulnerabilityKeys.privateKey,
|
|
);
|
|
const provenance = signedProviderV2(
|
|
{
|
|
schemaVersion: 2,
|
|
evidenceType: "provenance-attestation",
|
|
provider: "synthetic-provenance-provider",
|
|
signer: "synthetic-signer",
|
|
issuedAt: "2026-08-02T01:00:00.000Z",
|
|
expiresAt: "2026-08-02T02:00:00.000Z",
|
|
run: { ...run, invocationNonce: "2".repeat(64) },
|
|
source,
|
|
candidate,
|
|
subject: { name: "dist", digest: { sha256: candidate.distSha256 } },
|
|
},
|
|
"synthetic-provenance",
|
|
provenanceKeys.publicKey,
|
|
provenanceKeys.privateKey,
|
|
);
|
|
const vulnerabilityBytes = Buffer.from(`${JSON.stringify(vulnerability)}\n`);
|
|
const provenanceBytes = Buffer.from(`${JSON.stringify(provenance)}\n`);
|
|
const vulnerabilityTrust = trust("synthetic-vulnerability", vulnerabilityKeys.publicKey);
|
|
const provenanceTrust = trust("synthetic-provenance", provenanceKeys.publicKey);
|
|
const providerEvidence = {
|
|
vulnerabilityReportSha256: digestBytes(vulnerabilityBytes),
|
|
provenanceAttestationSha256: digestBytes(provenanceBytes),
|
|
vulnerabilityInvocationNonce: "1".repeat(64),
|
|
provenanceInvocationNonce: "2".repeat(64),
|
|
vulnerabilityKeyId: vulnerabilityTrust.keyId,
|
|
vulnerabilityKeyFingerprint: vulnerabilityTrust.publicKeyFingerprint,
|
|
provenanceKeyId: provenanceTrust.keyId,
|
|
provenanceKeyFingerprint: provenanceTrust.publicKeyFingerprint,
|
|
secretScanAttestation: vulnerability.secretScanAttestation,
|
|
};
|
|
const common = {
|
|
schemaVersion: 3,
|
|
verifiedAt: "2026-08-02T01:00:00.000Z",
|
|
status: "PASS",
|
|
verifier: {
|
|
id: "clean-architecture-frontend-template/promotion-verifier",
|
|
version: "3",
|
|
},
|
|
run,
|
|
source,
|
|
candidate,
|
|
providerEvidence,
|
|
trustPolicySha256: trustPolicySha256({ vulnerabilityTrust, provenanceTrust }),
|
|
failures: [],
|
|
};
|
|
const providerBytes = Buffer.from(
|
|
`${JSON.stringify({
|
|
...common,
|
|
artifactType: "provider-verification",
|
|
vulnerabilityStatus: "PASS",
|
|
provenanceAttestationStatus: "PASS",
|
|
}, null, 2)}\n`,
|
|
);
|
|
const promotionBytes = Buffer.from(
|
|
`${JSON.stringify({
|
|
...common,
|
|
artifactType: "promotion-verification",
|
|
localEvidenceStatus: "PASS",
|
|
localEvidenceAssessmentSha256: digest("synthetic-assessment"),
|
|
providerVerificationSha256: digestBytes(providerBytes),
|
|
}, null, 2)}\n`,
|
|
);
|
|
return {
|
|
files: {
|
|
"release-candidate.tar.gz": archiveBytes,
|
|
"vulnerability-report.json": vulnerabilityBytes,
|
|
"provenance-attestation.json": provenanceBytes,
|
|
"provider-verification.json": providerBytes,
|
|
"promotion-verification.json": promotionBytes,
|
|
},
|
|
verification: {
|
|
vulnerabilityTrust,
|
|
provenanceTrust,
|
|
nowEpochMs: () => Date.parse("2026-08-02T01:00:00.000Z"),
|
|
expected: {
|
|
run,
|
|
sourceRevision: source.revision,
|
|
sourceSetSha256: source.sourceSetSha256,
|
|
archiveSha256: candidate.archiveSha256,
|
|
bundleSha256: candidate.bundleSha256,
|
|
distSha256: candidate.distSha256,
|
|
lockfileSha256: candidate.lockfileSha256,
|
|
},
|
|
},
|
|
vulnerabilityPem: vulnerabilityKeys.publicKey.export({ type: "spki", format: "pem" }),
|
|
provenancePem: provenanceKeys.publicKey.export({ type: "spki", format: "pem" }),
|
|
};
|
|
}
|
|
|
|
function fingerprint(publicKey: KeyObject): string {
|
|
return `sha256:${createHash("sha256")
|
|
.update(publicKey.export({ type: "spki", format: "der" }))
|
|
.digest("hex")}`;
|
|
}
|
|
|
|
function trust(keyId: string, publicKey: KeyObject) {
|
|
return { keyId, publicKey, publicKeyFingerprint: fingerprint(publicKey) };
|
|
}
|
|
|
|
function signedProviderV2(
|
|
unsigned: Record<string, unknown>,
|
|
keyId: string,
|
|
publicKey: KeyObject,
|
|
privateKey: KeyObject,
|
|
fingerprintOverride?: string,
|
|
): Record<string, any> {
|
|
const { signature: existingSignature, ...payload } = unsigned;
|
|
const value = {
|
|
...payload,
|
|
signature: {
|
|
algorithm: "Ed25519" as const,
|
|
keyId,
|
|
publicKeyFingerprint:
|
|
fingerprintOverride ??
|
|
(existingSignature && typeof existingSignature === "object" &&
|
|
"publicKeyFingerprint" in existingSignature
|
|
? String(existingSignature.publicKeyFingerprint)
|
|
: fingerprint(publicKey)),
|
|
value: "",
|
|
},
|
|
};
|
|
value.signature.value = sign(
|
|
null,
|
|
providerEvidenceSignaturePayload(value),
|
|
privateKey,
|
|
).toString("base64");
|
|
return value;
|
|
}
|
|
|
|
function providerUnsigned(
|
|
kind: "vulnerability" | "provenance",
|
|
expected: ReturnType<typeof providerExpectedContext>,
|
|
): Record<string, any> {
|
|
const common = {
|
|
source: expected.source,
|
|
candidate: expected.candidate,
|
|
schemaVersion: 2,
|
|
evidenceType:
|
|
kind === "vulnerability"
|
|
? "vulnerability-report"
|
|
: "provenance-attestation",
|
|
provider: `fixture-${kind}`,
|
|
issuedAt: "2026-08-02T01:00:00.000Z",
|
|
expiresAt: "2026-08-02T02:00:00.000Z",
|
|
run: {
|
|
...expected.run,
|
|
invocationNonce: kind === "vulnerability" ? "1".repeat(64) : "2".repeat(64),
|
|
},
|
|
};
|
|
return kind === "vulnerability"
|
|
? {
|
|
...common,
|
|
secretScanAttestation: expected.secretScanAttestation,
|
|
findings: [],
|
|
}
|
|
: {
|
|
...common,
|
|
signer: "fixture-workload",
|
|
subject: {
|
|
name: "dist",
|
|
digest: { sha256: expected.candidate.distSha256 },
|
|
},
|
|
};
|
|
}
|
|
|
|
async function createArchivedAssessmentFixture(): Promise<{
|
|
root: string;
|
|
manifest: ReleaseCandidateManifest;
|
|
assessmentSha256: string;
|
|
}> {
|
|
const root = await mkdtemp(path.join(tmpdir(), "archived-assessment-"));
|
|
const sourceRevision = "a".repeat(40);
|
|
const sourceSetSha256 = digest("source set");
|
|
const releaseManifestBytes = Buffer.from(
|
|
`${JSON.stringify({
|
|
schemaVersion: 1,
|
|
appVersion: "1.0.0",
|
|
buildId: "build-1",
|
|
commitSha: sourceRevision,
|
|
configSchemaVersion: "1",
|
|
apiContractVersion: "1",
|
|
assetManifestHash: digest("vite manifest"),
|
|
releaseId: "release-1",
|
|
builtAt: "2026-08-02T00:00:00.000Z",
|
|
routeChunks: { home: "assets/home.js" },
|
|
})}\n`,
|
|
);
|
|
const distInputs = [
|
|
{ path: "dist/app.js", bytes: Buffer.byteLength("app\n"), sha256: digest("app\n"), gzipBytes: 0 },
|
|
{
|
|
path: "dist/release-manifest.json",
|
|
bytes: releaseManifestBytes.byteLength,
|
|
sha256: digestBytes(releaseManifestBytes),
|
|
gzipBytes: 0,
|
|
},
|
|
];
|
|
const candidateDist = distSha256(distInputs);
|
|
const sbomBytes = Buffer.from(
|
|
`${JSON.stringify({
|
|
bomFormat: "CycloneDX",
|
|
specVersion: "1.6",
|
|
serialNumber: "urn:uuid:00000000-0000-4000-8000-000000000001",
|
|
version: 1,
|
|
metadata: {
|
|
component: { type: "application", name: "fixture", version: "1.0.0" },
|
|
properties: [],
|
|
},
|
|
components: [],
|
|
dependencies: [],
|
|
})}\n`,
|
|
);
|
|
const sbomSha256 = digestBytes(sbomBytes);
|
|
const lockfileBytes = Buffer.from("lockfile\n");
|
|
const lockfileDigest = digestBytes(lockfileBytes);
|
|
const buildManifest = {
|
|
schemaVersion: 1,
|
|
buildId: "build-1",
|
|
commitSha: sourceRevision,
|
|
releaseId: "release-1",
|
|
moduleInventoryHash: digest("module inventory"),
|
|
generatedAt: "2026-08-02T00:00:00.000Z",
|
|
buildContext: {
|
|
nodeVersion: "v24.0.0",
|
|
packageManagerVersion: "11.0.0",
|
|
runnerImage: "linux-x64",
|
|
sourceDateEpoch: "1785638400",
|
|
},
|
|
outputs: {
|
|
directory: "dist",
|
|
viteManifest: "dist/.vite/manifest.json",
|
|
moduleInventory: "artifacts/quality/vite-module-inventory.json",
|
|
routeChunks: { home: "assets/home.js" },
|
|
runtimeConfigSchema: "dist/runtime-config.schema.json",
|
|
},
|
|
};
|
|
const provenance = {
|
|
_type: "https://in-toto.io/Statement/v1",
|
|
subject: [{ name: "dist", digest: { sha256: candidateDist } }],
|
|
predicateType: "https://slsa.dev/provenance/v1",
|
|
predicate: {
|
|
buildDefinition: {
|
|
buildType: "https://vite.dev/build/v1",
|
|
externalParameters: {},
|
|
internalParameters: {},
|
|
resolvedDependencies: [
|
|
{ uri: "pnpm-lock.yaml", digest: { sha256: lockfileDigest } },
|
|
],
|
|
},
|
|
runDetails: {
|
|
builder: { id: "fixture-builder" },
|
|
metadata: { invocationId: "LOCAL_UNSIGNED" },
|
|
},
|
|
materials: { lockfileSha256: lockfileDigest, sourceSetSha256, sbomSha256 },
|
|
},
|
|
};
|
|
const supplyVerification = {
|
|
schemaVersion: 1,
|
|
localStatus: "PASS",
|
|
promotionStatus: "FAIL_UNVERIFIED",
|
|
lockfileSha256: lockfileDigest,
|
|
sourceSetSha256,
|
|
distSha256: candidateDist,
|
|
sbomSha256,
|
|
dependencyDiff: { added: [], removed: [], changed: [], upgrades: [] },
|
|
highRiskReview: [],
|
|
vulnerabilityStatus: "FAIL_UNVERIFIED",
|
|
provenanceAttestationStatus: "FAIL_UNVERIFIED",
|
|
failures: [],
|
|
};
|
|
const members = new Map<string, Buffer>([
|
|
["dist/app.js", Buffer.from("app\n")],
|
|
["dist/release-manifest.json", releaseManifestBytes],
|
|
["pnpm-lock.yaml", lockfileBytes],
|
|
["artifacts/release/build-manifest.json", Buffer.from(`${JSON.stringify(buildManifest)}\n`)],
|
|
["artifacts/release/provenance.json", Buffer.from(`${JSON.stringify(provenance)}\n`)],
|
|
[
|
|
"artifacts/security/supply-chain-verification.json",
|
|
Buffer.from(`${JSON.stringify(supplyVerification)}\n`),
|
|
],
|
|
["artifacts/release/sbom.cdx.json", sbomBytes],
|
|
]);
|
|
const evidenceInputs = [...members.entries()]
|
|
.map(([memberPath, bytes]) => ({
|
|
path: memberPath,
|
|
bytes: bytes.byteLength,
|
|
sha256: digestBytes(bytes),
|
|
}))
|
|
.sort((left, right) => (left.path < right.path ? -1 : left.path > right.path ? 1 : 0));
|
|
const policyPaths = [
|
|
"config/security/dependency-baseline.approval.json",
|
|
"config/security/dependency-baseline.json",
|
|
"config/security/dependency-change-evidence.json",
|
|
"config/security/dependency-policy.json",
|
|
"config/security/secret-scan-policy.json",
|
|
"config/security/vulnerability-exceptions.json",
|
|
"config/security/vulnerability-policy.json",
|
|
"schemas/artifacts/build-manifest.schema.json",
|
|
"schemas/artifacts/dependency-inventory.schema.json",
|
|
"schemas/artifacts/supply-chain-verification.schema.json",
|
|
"scripts/contracts/release-artifacts.ts",
|
|
"scripts/create-release-candidate.ts",
|
|
"scripts/generate-supply-chain.ts",
|
|
"scripts/lib/build-manifest-outputs.ts",
|
|
"scripts/lib/json-schema.ts",
|
|
"scripts/lib/local-policy-evidence.ts",
|
|
"scripts/lib/local-release-evidence.ts",
|
|
"scripts/lib/release-candidate.ts",
|
|
"scripts/lib/release-input-evidence.ts",
|
|
"scripts/lib/release-runtime-coherence.ts",
|
|
"scripts/lib/repository-file-inventory.ts",
|
|
"scripts/lib/secret-scan-evaluator.ts",
|
|
"scripts/lib/secret-scan-policy.ts",
|
|
"scripts/lib/secret-scan.ts",
|
|
"scripts/lib/supply-chain.ts",
|
|
"scripts/lib/validated-json-artifact.ts",
|
|
"src/contracts/release-artifacts.ts",
|
|
"src/features/installed-contract-contributions.ts",
|
|
"src/features/installed-feature-contracts.ts",
|
|
];
|
|
const sbomRow = evidenceInputs.find(
|
|
({ path: memberPath }) => memberPath === "artifacts/release/sbom.cdx.json",
|
|
)!;
|
|
const policyInputs = policyPaths.map((policyPath) => ({
|
|
path: policyPath,
|
|
bytes: 2,
|
|
sha256: digest(`policy:${policyPath}`),
|
|
}));
|
|
const verifierPaths = new Set([
|
|
"scripts/contracts/release-artifacts.ts",
|
|
"scripts/create-release-candidate.ts",
|
|
"scripts/generate-supply-chain.ts",
|
|
"scripts/lib/build-manifest-outputs.ts",
|
|
"scripts/lib/json-schema.ts",
|
|
"scripts/lib/local-policy-evidence.ts",
|
|
"scripts/lib/local-release-evidence.ts",
|
|
"scripts/lib/release-candidate.ts",
|
|
"scripts/lib/release-input-evidence.ts",
|
|
"scripts/lib/release-runtime-coherence.ts",
|
|
"scripts/lib/repository-file-inventory.ts",
|
|
"scripts/lib/secret-scan-evaluator.ts",
|
|
"scripts/lib/secret-scan-policy.ts",
|
|
"scripts/lib/secret-scan.ts",
|
|
"scripts/lib/supply-chain.ts",
|
|
"scripts/lib/validated-json-artifact.ts",
|
|
"src/contracts/release-artifacts.ts",
|
|
"src/features/installed-contract-contributions.ts",
|
|
"src/features/installed-feature-contracts.ts",
|
|
]);
|
|
const assessment = localEvidenceAssessmentArtifactSchema.parse({
|
|
...passingAssessment(),
|
|
verifier: {
|
|
id: "clean-architecture-frontend-template/local-evidence-verifier",
|
|
version: "1",
|
|
sourceSha256: supplyChainDigest(
|
|
policyInputs.filter(({ path: policyPath }) => verifierPaths.has(policyPath)),
|
|
),
|
|
},
|
|
source: { revision: sourceRevision, sourceSetSha256 },
|
|
candidate: {
|
|
distSha256: candidateDist,
|
|
lockfileSha256: evidenceInputs.find(({ path: memberPath }) => memberPath === "pnpm-lock.yaml")!
|
|
.sha256,
|
|
sbomSha256: sbomRow.sha256,
|
|
},
|
|
policyInputs,
|
|
evidenceInputs,
|
|
});
|
|
const assessmentBytes = Buffer.from(`${JSON.stringify(assessment)}\n`);
|
|
members.set(LOCAL_EVIDENCE_ASSESSMENT_PATH, assessmentBytes);
|
|
for (const [memberPath, bytes] of members) {
|
|
await mkdir(path.dirname(path.join(root, memberPath)), { recursive: true });
|
|
await writeFile(path.join(root, memberPath), bytes);
|
|
}
|
|
const files = [...members.entries()]
|
|
.map(([memberPath, bytes]) => ({
|
|
path: memberPath,
|
|
bytes: bytes.byteLength,
|
|
sha256: digestBytes(bytes),
|
|
}))
|
|
.sort((left, right) => (left.path < right.path ? -1 : left.path > right.path ? 1 : 0));
|
|
const manifest: ReleaseCandidateManifest = {
|
|
schemaVersion: 1,
|
|
distSha256: assessment.candidate.distSha256,
|
|
lockfileSha256: assessment.candidate.lockfileSha256,
|
|
bundleSha256: supplyChainDigest(files),
|
|
files,
|
|
};
|
|
await mkdir(path.join(root, "artifacts/release"), { recursive: true });
|
|
await writeFile(
|
|
path.join(root, "artifacts/release/release-candidate.json"),
|
|
`${JSON.stringify(manifest)}\n`,
|
|
);
|
|
return { root, manifest, assessmentSha256: digestBytes(assessmentBytes) };
|
|
}
|