149 lines
4.0 KiB
JavaScript
149 lines
4.0 KiB
JavaScript
import { mkdir, writeFile } from "node:fs/promises";
|
|
|
|
import {
|
|
diffDependencyInventories,
|
|
isValidSha512Integrity,
|
|
supplyChainDigest,
|
|
validateDependencyReview,
|
|
validateLicensePolicy,
|
|
validateVulnerabilityReport,
|
|
verifySupplyChainCoherence,
|
|
} from "./lib/supply-chain.mjs";
|
|
|
|
const integrity = `sha512-${Buffer.alloc(64, 1).toString("base64")}`;
|
|
const baseDependency = {
|
|
name: "base",
|
|
version: "1.0.0",
|
|
direct: false,
|
|
scope: "production",
|
|
optional: false,
|
|
license: "MIT",
|
|
integrity,
|
|
dependencies: [],
|
|
};
|
|
const directDependency = {
|
|
...baseDependency,
|
|
name: "new-direct",
|
|
direct: true,
|
|
};
|
|
const before = { dependencies: [baseDependency] };
|
|
const after = { dependencies: [baseDependency, directDependency] };
|
|
const diff = diffDependencyInventories(before, after);
|
|
const selfReview = validateDependencyReview(diff, after, {
|
|
changes: [
|
|
{
|
|
changeId: "add:new-direct@1.0.0",
|
|
owner: "same-person",
|
|
reviewer: "same-person",
|
|
reason: "fixture",
|
|
rollback: "remove",
|
|
},
|
|
],
|
|
});
|
|
const deniedLicense = validateLicensePolicy(
|
|
{
|
|
dependencies: [{ ...baseDependency, license: "AGPL-3.0" }],
|
|
},
|
|
{
|
|
allowedLicenses: ["MIT"],
|
|
deniedLicensePatterns: ["AGPL"],
|
|
},
|
|
);
|
|
const vulnerable = validateVulnerabilityReport(
|
|
{
|
|
provider: "fixture",
|
|
scannedLockfileSha256: "lock",
|
|
findings: [
|
|
{
|
|
id: "CVE-FIXTURE",
|
|
packageName: "base",
|
|
version: "1.0.0",
|
|
severity: "critical",
|
|
},
|
|
],
|
|
},
|
|
{ blockAtSeverity: "high" },
|
|
{
|
|
exceptions: [
|
|
{
|
|
vulnerabilityId: "CVE-FIXTURE",
|
|
packageName: "base",
|
|
owner: "owner",
|
|
reviewer: "reviewer",
|
|
reason: "expired fixture",
|
|
expiresAt: "2000-01-01T00:00:00.000Z",
|
|
},
|
|
],
|
|
},
|
|
"lock",
|
|
new Date("2026-07-26T00:00:00.000Z"),
|
|
);
|
|
const mismatchedCoherence = verifySupplyChainCoherence(
|
|
{
|
|
components: [],
|
|
metadata: {
|
|
properties: [{ name: "ca:lockfileSha256", value: "wrong" }],
|
|
},
|
|
},
|
|
{ dependencies: [baseDependency], lockfileSha256: "lock" },
|
|
{
|
|
subject: [{ digest: { sha256: "wrong" } }],
|
|
predicate: { materials: { lockfileSha256: "wrong" } },
|
|
},
|
|
"dist",
|
|
);
|
|
const orderingStable =
|
|
supplyChainDigest({ dependencies: [baseDependency, directDependency] }) ===
|
|
supplyChainDigest({ dependencies: [directDependency, baseDependency] });
|
|
const approvedDigest = supplyChainDigest(before);
|
|
const tamperedBaselineRejected =
|
|
approvedDigest !==
|
|
supplyChainDigest({
|
|
dependencies: [{ ...baseDependency, version: "9.9.9-tampered" }],
|
|
});
|
|
const providerFailure = validateVulnerabilityReport(
|
|
{
|
|
provider: "",
|
|
scannedLockfileSha256: "wrong",
|
|
findings: [],
|
|
},
|
|
{ blockAtSeverity: "high" },
|
|
{ exceptions: [] },
|
|
"lock",
|
|
);
|
|
const results = [
|
|
{
|
|
id: "transitive-removal-is-real-diff",
|
|
passed:
|
|
diffDependencyInventories(after, before).removed[0] ===
|
|
"new-direct@1.0.0",
|
|
},
|
|
{
|
|
id: "tampered-integrity-rejected",
|
|
passed: !isValidSha512Integrity("sha512-dGFtcGVyZWQ="),
|
|
},
|
|
{ id: "high-risk-self-approval-rejected", passed: !selfReview.passed },
|
|
{ id: "denied-license-rejected", passed: !deniedLicense.passed },
|
|
{
|
|
id: "critical-vulnerability-expired-exception-rejected",
|
|
passed: !vulnerable.passed,
|
|
},
|
|
{ id: "sbom-provenance-mismatch-rejected", passed: !mismatchedCoherence.passed },
|
|
{ id: "dependency-ordering-deterministic", passed: orderingStable },
|
|
{ id: "baseline-digest-tamper-rejected", passed: tamperedBaselineRejected },
|
|
{
|
|
id: "vulnerability-provider-evidence-invalid",
|
|
passed: !providerFailure.passed,
|
|
},
|
|
];
|
|
await mkdir("artifacts/security", { recursive: true });
|
|
await writeFile(
|
|
"artifacts/security/supply-chain-fixtures.json",
|
|
`${JSON.stringify({ schemaVersion: 1, results }, null, 2)}\n`,
|
|
);
|
|
if (results.some((result) => !result.passed)) {
|
|
process.stderr.write("Supply-chain negative fixture failed.\n");
|
|
process.exit(1);
|
|
}
|
|
process.stdout.write(`Supply-chain fixtures: ${results.length} PASS\n`);
|