1854 lines
69 KiB
TypeScript
1854 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";
|
|
|
|
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: "tech-log-frontend/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 });
|
|
}
|
|
});
|
|
});
|
|
|
|
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: "tech-log-frontend/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",
|
|
"scripts/lib/vite-route-chunks.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",
|
|
"scripts/lib/vite-route-chunks.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: "tech-log-frontend/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) };
|
|
}
|