179 lines
5.5 KiB
TypeScript
179 lines
5.5 KiB
TypeScript
import { readFile } from "node:fs/promises";
|
|
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
import {
|
|
diffDependencyInventories,
|
|
isValidSha512Integrity,
|
|
parsePnpmLockfilePackages,
|
|
supplyChainDigest,
|
|
validateDependencyReview,
|
|
validateLicensePolicy,
|
|
} from "../../scripts/lib/supply-chain.ts";
|
|
import { digestReleaseInputFiles } from "../../scripts/lib/release-input-evidence.ts";
|
|
import { findSecretMatches } from "../../scripts/lib/secret-scan.ts";
|
|
|
|
const integrity = `sha512-${Buffer.alloc(64, 7).toString("base64")}`;
|
|
const dependency = {
|
|
name: "fixture",
|
|
version: "1.0.0",
|
|
direct: true,
|
|
scope: "production",
|
|
optional: false,
|
|
license: "MIT",
|
|
integrity,
|
|
dependencies: [],
|
|
};
|
|
|
|
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]) {
|
|
expect(source).toContain("buildRepositoryFileInventory");
|
|
expect(source).not.toContain("async function filesWithin");
|
|
}
|
|
});
|
|
|
|
it("binds provenance digest behavior to tracked files outside policy roots", async () => {
|
|
const contents = new Map([
|
|
["src/app.ts", Buffer.from("app\n")],
|
|
["README.md", Buffer.from("one\n")],
|
|
]);
|
|
const first = await digestReleaseInputFiles(
|
|
["README.md", "src/app.ts"],
|
|
async (file) => contents.get(file)!,
|
|
);
|
|
contents.set("README.md", Buffer.from("two\n"));
|
|
const second = await digestReleaseInputFiles(
|
|
["README.md", "src/app.ts"],
|
|
async (file) => contents.get(file)!,
|
|
);
|
|
expect(second).not.toBe(first);
|
|
});
|
|
|
|
it("detects every forbidden secret fixture, including quoted JSON keys", async () => {
|
|
const fixtureRoot = "tests/fixtures/security/secret-detection/forbidden";
|
|
const findings = (
|
|
await Promise.all(
|
|
["source.ts", "dist.ts", "config.json"].map(async (file) =>
|
|
findSecretMatches(
|
|
`${fixtureRoot}/${file}`,
|
|
await readFile(`${fixtureRoot}/${file}`, "utf8"),
|
|
),
|
|
),
|
|
)
|
|
).flat();
|
|
expect(findings.map((finding) => [finding.file, finding.ruleId])).toEqual([
|
|
[`${fixtureRoot}/source.ts`, "aws-access-key"],
|
|
[`${fixtureRoot}/dist.ts`, "assigned-secret"],
|
|
[`${fixtureRoot}/config.json`, "assigned-secret"],
|
|
]);
|
|
});
|
|
|
|
it("covers every mandatory release input in the secret scan policy", async () => {
|
|
const policy = JSON.parse(
|
|
await readFile("config/security/secret-scan-policy.json", "utf8"),
|
|
) as { trackedRoots: string[] };
|
|
expect(policy.trackedRoots).toEqual(
|
|
expect.arrayContaining([
|
|
"index.html",
|
|
".dependency-cruiser.json",
|
|
".nvmrc",
|
|
".npmrc",
|
|
"eslint.config.ts",
|
|
"package.json",
|
|
"pnpm-lock.yaml",
|
|
"pnpm-workspace.yaml",
|
|
"scripts",
|
|
"schemas",
|
|
"config",
|
|
".gitea/workflows/quality-gates.yml",
|
|
"vite.config.ts",
|
|
"vite.service-worker.config.ts",
|
|
"vitest.config.ts",
|
|
"playwright.config.ts",
|
|
"playwright.capabilities.config.ts",
|
|
"playwright.dev.config.ts",
|
|
"playwright.storybook.config.ts",
|
|
"playwright.visual.config.ts",
|
|
"tsconfig.json",
|
|
"tsconfig.app.json",
|
|
"tsconfig.base.json",
|
|
"tsconfig.node.json",
|
|
"tsconfig.recipes.json",
|
|
"tsconfig.service-worker.json",
|
|
"tsconfig.test.json",
|
|
"tsconfig.web-worker.json",
|
|
]),
|
|
);
|
|
});
|
|
|
|
it("parses every top-level lockfile package and validates SRI", () => {
|
|
const parsed = parsePnpmLockfilePackages(`
|
|
packages:
|
|
|
|
'@scope/one@1.0.0':
|
|
resolution: {integrity: ${integrity}}
|
|
|
|
two@2.0.0:
|
|
resolution: {integrity: ${integrity}}
|
|
|
|
snapshots:
|
|
`);
|
|
expect(parsed).toEqual([
|
|
{ name: "@scope/one", version: "1.0.0", integrity },
|
|
{ name: "two", version: "2.0.0", integrity },
|
|
]);
|
|
expect(parsed.every((entry) => isValidSha512Integrity(entry.integrity))).toBe(
|
|
true,
|
|
);
|
|
});
|
|
|
|
it("keeps inventory digests stable when dependency ordering changes", () => {
|
|
const other = { ...dependency, name: "other" };
|
|
expect(supplyChainDigest([dependency, other])).toBe(
|
|
supplyChainDigest([other, dependency]),
|
|
);
|
|
});
|
|
|
|
it("calculates actual additions and requires independent high-risk review", () => {
|
|
const before = { dependencies: [] };
|
|
const after = { dependencies: [dependency] };
|
|
const diff = diffDependencyInventories(before, after);
|
|
expect(diff.added).toEqual(["fixture@1.0.0"]);
|
|
expect(
|
|
validateDependencyReview(diff, after, {
|
|
changes: [
|
|
{
|
|
changeId: "add:fixture@1.0.0",
|
|
owner: "one",
|
|
reviewer: "one",
|
|
reason: "fixture",
|
|
rollback: "remove",
|
|
},
|
|
],
|
|
}).passed,
|
|
).toBe(false);
|
|
});
|
|
|
|
it("allows explicit policy licenses and rejects denied licenses", () => {
|
|
expect(
|
|
validateLicensePolicy(
|
|
{ dependencies: [dependency] },
|
|
{ allowedLicenses: ["MIT"], deniedLicensePatterns: ["AGPL"] },
|
|
).passed,
|
|
).toBe(true);
|
|
expect(
|
|
validateLicensePolicy(
|
|
{
|
|
dependencies: [{ ...dependency, license: "AGPL-3.0" }],
|
|
},
|
|
{ allowedLicenses: ["MIT"], deniedLicensePatterns: ["AGPL"] },
|
|
).passed,
|
|
).toBe(false);
|
|
});
|
|
});
|