fix: recompute local promotion evidence
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
import { readFile } from "node:fs/promises";
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
compareStoredDependencyEvidence,
|
||||
compareStoredLicenseEvidence,
|
||||
distChecksumsText,
|
||||
recomputeDependencyEvidence,
|
||||
recomputeLicenseEvidence,
|
||||
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",
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -675,11 +675,14 @@ describe("supply-chain policy", () => {
|
||||
});
|
||||
|
||||
it("uses one fail-closed repository inventory for provenance and secret scanning", async () => {
|
||||
const [provenanceSource, securitySource] = await Promise.all([
|
||||
readFile("scripts/generate-supply-chain.ts", "utf8"),
|
||||
readFile("scripts/security-scan.ts", "utf8"),
|
||||
]);
|
||||
for (const source of [provenanceSource, securitySource]) {
|
||||
const [provenanceSource, securityCliSource, securityEvaluatorSource] =
|
||||
await Promise.all([
|
||||
readFile("scripts/generate-supply-chain.ts", "utf8"),
|
||||
readFile("scripts/security-scan.ts", "utf8"),
|
||||
readFile("scripts/lib/secret-scan-evaluator.ts", "utf8"),
|
||||
]);
|
||||
expect(securityCliSource).toContain("evaluateRepositorySecretScan");
|
||||
for (const source of [provenanceSource, securityEvaluatorSource]) {
|
||||
expect(source).toContain("buildRepositoryFileInventory");
|
||||
expect(source).not.toContain("async function filesWithin");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user