253 lines
7.6 KiB
TypeScript
253 lines
7.6 KiB
TypeScript
import { readFile } from "node:fs/promises";
|
|
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
import {
|
|
supplyChainVerificationArtifactSchema,
|
|
vulnerabilityReportArtifactSchema,
|
|
} from "../../scripts/contracts/release-artifacts.ts";
|
|
import {
|
|
compareStoredDependencyEvidence,
|
|
compareStoredLicenseEvidence,
|
|
compareStoredLocalVulnerabilityReport,
|
|
distChecksumsText,
|
|
recomputeDependencyEvidence,
|
|
recomputeLicenseEvidence,
|
|
verifyLocalSupplyChainDefaults,
|
|
verifyStoredDistChecksums,
|
|
} from "../../scripts/lib/local-policy-evidence.ts";
|
|
import {
|
|
evaluateSecretScan,
|
|
parseSecretScanPolicy,
|
|
secretScanSarifSchema,
|
|
verifyStoredSecretScan,
|
|
} from "../../scripts/lib/secret-scan-evaluator.ts";
|
|
import { supplyChainDigest } from "../../scripts/lib/supply-chain.ts";
|
|
|
|
const sha512Integrity = `sha512-${Buffer.alloc(64, 7).toString("base64")}`;
|
|
const dependency = {
|
|
name: "fixture",
|
|
version: "1.0.0",
|
|
direct: true,
|
|
scope: "production" as const,
|
|
optional: false,
|
|
license: "MIT",
|
|
integrity: sha512Integrity,
|
|
dependencies: [],
|
|
};
|
|
const inventory = {
|
|
schemaVersion: 2 as const,
|
|
packageManager: "pnpm@11.17.0",
|
|
lockfileSha256: "1".repeat(64),
|
|
dependencyCount: 1,
|
|
directDependencyCount: 1,
|
|
dependencies: [dependency],
|
|
};
|
|
|
|
describe("recomputed local promotion evidence", () => {
|
|
it("rejects a schema-valid dependency report with a forged semantic digest", () => {
|
|
const baseline = { ...inventory, dependencies: [] };
|
|
const recomputed = recomputeDependencyEvidence({
|
|
inventory,
|
|
baseline,
|
|
baselineApproval: {
|
|
schemaVersion: 1,
|
|
snapshotDigest: supplyChainDigest(baseline),
|
|
owner: "platform-security",
|
|
},
|
|
dependencyChangeEvidence: {
|
|
changes: [
|
|
{
|
|
changeId: "add:fixture@1.0.0",
|
|
owner: "dependency-owner",
|
|
reviewer: "security-reviewer",
|
|
reason: "fixture",
|
|
rollback: "remove fixture",
|
|
},
|
|
],
|
|
},
|
|
});
|
|
const tampered = {
|
|
...recomputed.report,
|
|
currentDigest: "f".repeat(64),
|
|
};
|
|
|
|
expect(compareStoredDependencyEvidence(recomputed, tampered)).toContain(
|
|
"stored dependency diff does not match recomputed policy evidence",
|
|
);
|
|
});
|
|
|
|
it("rejects a schema-valid PASS license report when policy recomputes FAIL", () => {
|
|
const recomputed = recomputeLicenseEvidence({
|
|
inventory,
|
|
policy: {
|
|
allowedLicenses: ["Apache-2.0"],
|
|
deniedLicensePatterns: ["MIT"],
|
|
},
|
|
});
|
|
const tampered = {
|
|
schemaVersion: 1 as const,
|
|
status: "PASS" as const,
|
|
dependencyCount: 1,
|
|
results: [
|
|
{
|
|
package: "fixture@1.0.0",
|
|
license: "MIT",
|
|
passed: true,
|
|
reason: null,
|
|
},
|
|
],
|
|
failures: [],
|
|
};
|
|
|
|
expect(compareStoredLicenseEvidence(recomputed, tampered)).toContain(
|
|
"stored license report does not match recomputed policy evidence",
|
|
);
|
|
});
|
|
|
|
it("rejects a checksum document that does not exactly describe sorted dist outputs", () => {
|
|
const outputs = [
|
|
{
|
|
path: "dist/z.js",
|
|
bytes: 1,
|
|
gzipBytes: 21,
|
|
sha256: "a".repeat(64),
|
|
},
|
|
{
|
|
path: "dist/a.js",
|
|
bytes: 1,
|
|
gzipBytes: 21,
|
|
sha256: "b".repeat(64),
|
|
},
|
|
];
|
|
expect(distChecksumsText(outputs)).toBe(
|
|
`${"b".repeat(64)} dist/a.js\n${"a".repeat(64)} dist/z.js\n`,
|
|
);
|
|
expect(
|
|
verifyStoredDistChecksums(outputs, `${"c".repeat(64)} dist/a.js\n`),
|
|
).toEqual(["stored dist checksums do not match current outputs"]);
|
|
});
|
|
|
|
it("rejects an empty schema-valid SARIF when real fixture source contains secrets", async () => {
|
|
const rawPolicy: unknown = JSON.parse(
|
|
await readFile(
|
|
"tests/fixtures/security/secret-detection/forbidden-policy.json",
|
|
"utf8",
|
|
),
|
|
);
|
|
const policy = parseSecretScanPolicy(rawPolicy);
|
|
const inventoryFiles = [
|
|
"tests/fixtures/security/secret-detection/forbidden/config.json",
|
|
"tests/fixtures/security/secret-detection/forbidden/dist.ts",
|
|
"tests/fixtures/security/secret-detection/forbidden/source.ts",
|
|
];
|
|
const evaluation = await evaluateSecretScan({
|
|
policy,
|
|
inventoryFiles,
|
|
readText: (file) => readFile(file, "utf8"),
|
|
now: Date.parse("2026-08-02T00:00:00.000Z"),
|
|
});
|
|
const fakeEmptySarif = secretScanSarifSchema.parse({
|
|
version: "2.1.0",
|
|
$schema: "https://json.schemastore.org/sarif-2.1.0.json",
|
|
runs: [
|
|
{
|
|
tool: {
|
|
driver: {
|
|
name: "ca-frontend-secret-scan",
|
|
rules: [
|
|
"private-key",
|
|
"aws-access-key",
|
|
"github-token",
|
|
"assigned-secret",
|
|
].map((id) => ({
|
|
id,
|
|
shortDescription: { text: "Potential credential material" },
|
|
})),
|
|
},
|
|
},
|
|
results: [],
|
|
},
|
|
],
|
|
});
|
|
|
|
expect(evaluation.findings).toHaveLength(3);
|
|
expect(verifyStoredSecretScan(evaluation, fakeEmptySarif)).toEqual(
|
|
expect.arrayContaining([
|
|
"recomputed secret scan contains 3 blocking result(s)",
|
|
"stored secret scan SARIF does not match recomputed results",
|
|
]),
|
|
);
|
|
});
|
|
|
|
it("rejects schema-valid verified provider statuses in local supply-chain evidence", () => {
|
|
const localVerification = {
|
|
schemaVersion: 1 as const,
|
|
localStatus: "PASS" as const,
|
|
promotionStatus: "FAIL_UNVERIFIED" as const,
|
|
lockfileSha256: "1".repeat(64),
|
|
sourceSetSha256: "2".repeat(64),
|
|
distSha256: "3".repeat(64),
|
|
sbomSha256: "4".repeat(64),
|
|
dependencyDiff: {
|
|
added: [],
|
|
removed: [],
|
|
changed: [],
|
|
upgrades: [],
|
|
},
|
|
highRiskReview: [],
|
|
vulnerabilityStatus: "FAIL_UNVERIFIED" as const,
|
|
provenanceAttestationStatus: "FAIL_UNVERIFIED" as const,
|
|
failures: [],
|
|
};
|
|
const tampered = [
|
|
{ ...localVerification, promotionStatus: "PASS" as const },
|
|
{ ...localVerification, vulnerabilityStatus: "PASS" as const },
|
|
{
|
|
...localVerification,
|
|
provenanceAttestationStatus: "PASS" as const,
|
|
},
|
|
].map((value) => supplyChainVerificationArtifactSchema.parse(value));
|
|
|
|
for (const value of tampered) {
|
|
expect(verifyLocalSupplyChainDefaults(value)).toEqual([
|
|
"supply-chain verification provider defaults are not local FAIL_UNVERIFIED",
|
|
]);
|
|
}
|
|
});
|
|
|
|
it("rejects every schema-valid drift from the exact local vulnerability report", () => {
|
|
const currentLockfileSha256 = "1".repeat(64);
|
|
const localReport = {
|
|
schemaVersion: 1 as const,
|
|
provider: "UNCONFIGURED",
|
|
scannedLockfileSha256: currentLockfileSha256,
|
|
status: "FAIL_UNVERIFIED" as const,
|
|
findings: [],
|
|
exceptionsApplied: [],
|
|
failures: ["external vulnerability provider report is missing"],
|
|
blocking: [],
|
|
};
|
|
const tampered = [
|
|
{ ...localReport, provider: "forged-provider" },
|
|
{ ...localReport, scannedLockfileSha256: "2".repeat(64) },
|
|
{ ...localReport, status: "PASS" as const },
|
|
{ ...localReport, findings: [{ id: "forged" }] },
|
|
{ ...localReport, exceptionsApplied: [{ id: "forged" }] },
|
|
{ ...localReport, failures: [] },
|
|
{ ...localReport, blocking: ["forged"] },
|
|
].map((value) => vulnerabilityReportArtifactSchema.parse(value));
|
|
|
|
for (const value of tampered) {
|
|
expect(
|
|
compareStoredLocalVulnerabilityReport(
|
|
currentLockfileSha256,
|
|
value,
|
|
),
|
|
).toEqual([
|
|
"local vulnerability report does not match exact unconfigured defaults",
|
|
]);
|
|
}
|
|
});
|
|
});
|