Files
tech-log-frontend/tests/unit/supply-chain.test.ts
T
DongHyeonkaandClaude Opus 5 bdee07a93b chore: sync the frontend template from a0fbafb to 5434760
Carries eight template commits: the provider sandbox actually running, release
admission to a named environment, the product feature manifest with its runtime
kill switch, architecture and documentation rules that match what is enforced,
the removability fixtures, and the browser, visual and performance evidence.

Product identity is unchanged. `package.json` keeps `tech-log-frontend` and the
catalog keeps the Tech Log naming; the home page was not in the delta. The
visual baselines are this product's own — the template's were excluded from the
transplant and these were regenerated here, where the only difference is the
platform overview's new product-feature section.

What this repository gains operationally: `config/runtime/{local,development,
staging,production}.json` with `FE-GATE-027` refusing an artifact whose runtime
document does not match the environment it is being admitted to, and
`FEATURE_OVERRIDES` for taking an installed feature out of service without a
rebuild.

Verified here: eight gates green, build green, visual 5/5, and 1,858 of 1,859
tests in the suites that do not need a sandbox — the one failure passes in
isolation and is a jsdom lazy-chunk timeout under parallel load. The provider
suites cannot run on this machine at all: `kernel.apparmor_restrict_unprivileged
_userns=1` makes `bwrap --unshare-net` fail, reproducible without any code from
either repository.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 21:34:19 +09:00

646 lines
22 KiB
TypeScript

import { createHash, generateKeyPairSync, sign } from "node:crypto";
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
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 { isReducedCiContractRun } from "../../scripts/contracts/ci-gates.ts";
import { findSecretMatches } from "../../scripts/lib/secret-scan.ts";
import {
parseSecretScanIncludedPaths,
selectIncludedInventoryFiles,
} from "../../scripts/lib/secret-scan-policy.ts";
import { checkSecurityFixtures } from "../../scripts/lib/security-fixture-check.ts";
import {
evaluatePromotionEvidence,
providerEvidenceSignaturePayload,
providerPublicKeyFingerprint,
} from "../../scripts/lib/provider-evidence.ts";
import {
createReleaseCandidateManifest,
RELEASE_CANDIDATE_EVIDENCE_PATHS,
verifyReleaseCandidate,
} from "../../scripts/lib/release-candidate.ts";
import { deterministicSupplyChainGeneratedAt } from "../../scripts/lib/supply-chain-time.ts";
import {
indexCiGateContract,
loadCiGateContract,
} from "../../scripts/contracts/ci-gates.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: [],
};
const candidateDistSha256 = "1".repeat(64);
const lockfileSha256 = "2".repeat(64);
const NOW = Date.parse("2026-08-02T01:00:00.000Z");
const sourceIdentity = Object.freeze({
revision: "a".repeat(40),
sourceSetSha256: "b".repeat(64),
});
const expectedProviderContext = Object.freeze({
run: Object.freeze({ id: "fixture-run", attempt: 1 }),
source: sourceIdentity,
candidate: Object.freeze({
archiveSha256: "3".repeat(64),
bundleSha256: "4".repeat(64),
distSha256: candidateDistSha256,
lockfileSha256,
}),
vulnerabilityInvocationNonce: "5".repeat(64),
provenanceInvocationNonce: "6".repeat(64),
secretScanAttestation: Object.freeze({
status: "PASS" as const,
localEvidenceAssessmentSha256: "7".repeat(64),
sourceSetSha256: sourceIdentity.sourceSetSha256,
policySha256: "8".repeat(64),
sarifSha256: "9".repeat(64),
scanInputSha256: "a".repeat(64),
}),
});
function signedProviderEvidence(
value: Record<string, unknown>,
keyId: string,
privateKey: ReturnType<typeof generateKeyPairSync>["privateKey"],
publicKeyFingerprint: string,
) {
return {
...value,
signature: {
algorithm: "Ed25519",
keyId,
publicKeyFingerprint,
value: sign(
null,
providerEvidenceSignaturePayload(value),
privateKey,
).toString("base64"),
},
};
}
function providerPair(input: Readonly<{
vulnerabilityKeys: ReturnType<typeof generateKeyPairSync>;
provenanceKeys: ReturnType<typeof generateKeyPairSync>;
candidate?: typeof expectedProviderContext.candidate;
vulnerabilityFingerprint?: string;
provenanceFingerprint?: string;
}>) {
const candidate = input.candidate ?? expectedProviderContext.candidate;
const vulnerabilityFingerprint = input.vulnerabilityFingerprint ??
providerPublicKeyFingerprint(input.vulnerabilityKeys.publicKey);
const provenanceFingerprint = input.provenanceFingerprint ??
providerPublicKeyFingerprint(input.provenanceKeys.publicKey);
return {
vulnerabilityReport: signedProviderEvidence({
schemaVersion: 2,
evidenceType: "vulnerability-report",
provider: "fixture-vulnerability-provider",
issuedAt: "2026-08-02T01:00:00.000Z",
expiresAt: "2026-08-02T02:00:00.000Z",
run: { ...expectedProviderContext.run, invocationNonce: expectedProviderContext.vulnerabilityInvocationNonce },
source: expectedProviderContext.source,
candidate,
secretScanAttestation: expectedProviderContext.secretScanAttestation,
findings: [],
}, "fixture-vulnerability-key", input.vulnerabilityKeys.privateKey, vulnerabilityFingerprint),
provenanceAttestation: signedProviderEvidence({
schemaVersion: 2,
evidenceType: "provenance-attestation",
provider: "fixture-provenance-provider",
issuedAt: "2026-08-02T01:00:00.000Z",
expiresAt: "2026-08-02T02:00:00.000Z",
run: { ...expectedProviderContext.run, invocationNonce: expectedProviderContext.provenanceInvocationNonce },
source: expectedProviderContext.source,
candidate,
signer: "fixture-workload-identity",
subject: { name: "dist", digest: { sha256: candidate.distSha256 } },
}, "fixture-provenance-key", input.provenanceKeys.privateKey, provenanceFingerprint),
};
}
function providerTrust(
keyId: string,
publicKey: ReturnType<typeof generateKeyPairSync>["publicKey"],
publicKeyFingerprint = providerPublicKeyFingerprint(publicKey),
) {
return { keyId, publicKey, publicKeyFingerprint };
}
describe("supply-chain policy", () => {
it("derives a stable supply-chain timestamp from the immutable build epoch", () => {
const input = {
generatedAt: "2026-08-01T00:00:00.000Z",
sourceDateEpoch: "1785542400",
};
expect(deterministicSupplyChainGeneratedAt(input)).toBe(
"2026-08-01T00:00:00.000Z",
);
expect(deterministicSupplyChainGeneratedAt(input)).toBe(
deterministicSupplyChainGeneratedAt({ ...input }),
);
expect(() =>
deterministicSupplyChainGeneratedAt({
generatedAt: "not-a-time",
sourceDateEpoch: "1785542400",
}),
).toThrow(/generatedAt/u);
expect(() =>
deterministicSupplyChainGeneratedAt({
generatedAt: "2026-08-01T00:00:00.000Z",
sourceDateEpoch: "1785542401",
}),
).toThrow(/SOURCE_DATE_EPOCH/u);
});
it("rejects release candidate dist bytes changed after manifest creation", async () => {
const root = await mkdtemp(path.join(tmpdir(), "release-candidate-"));
try {
const rawLockfile = "lockfileVersion: '9.0'\n";
const rawLockfileSha256 = createHash("sha256")
.update(rawLockfile)
.digest("hex");
await mkdir(path.join(root, "dist/.vite"), { recursive: true });
await writeFile(path.join(root, "dist/app.js"), "immutable\n");
await writeFile(path.join(root, "dist/.vite/metadata.json"), "{}\n");
await writeFile(path.join(root, "pnpm-lock.yaml"), rawLockfile);
for (const file of RELEASE_CANDIDATE_EVIDENCE_PATHS) {
if (file === "pnpm-lock.yaml") continue;
await mkdir(path.dirname(path.join(root, file)), { recursive: true });
await writeFile(
path.join(root, file),
file === "artifacts/release/dependency-inventory.json"
? `${JSON.stringify({ lockfileSha256: rawLockfileSha256 })}\n`
: `${file}\n`,
);
}
const manifest = await createReleaseCandidateManifest(root);
expect(manifest.lockfileSha256).toBe(rawLockfileSha256);
expect(manifest.files).toContainEqual(
expect.objectContaining({
path: "pnpm-lock.yaml",
sha256: rawLockfileSha256,
}),
);
expect(await createReleaseCandidateManifest(root)).toEqual(manifest);
expect((await verifyReleaseCandidate(manifest, root)).failures).toEqual(
[],
);
await writeFile(path.join(root, "dist/app.js"), "mutated\n");
expect(
(await verifyReleaseCandidate(manifest, root)).failures,
).toEqual(
expect.arrayContaining([
"release candidate dist digest mismatch",
"release candidate bundle digest mismatch",
"release candidate file set or file digest mismatch",
]),
);
} finally {
await rm(root, { recursive: true, force: true });
}
});
it("rejects a dependency inventory digest that differs from raw pnpm-lock bytes", async () => {
const root = await mkdtemp(path.join(tmpdir(), "release-lockfile-"));
try {
await mkdir(path.join(root, "dist"), { recursive: true });
await writeFile(path.join(root, "dist/app.js"), "immutable\n");
await writeFile(path.join(root, "pnpm-lock.yaml"), "lockfileVersion: '9.0'\n");
for (const file of RELEASE_CANDIDATE_EVIDENCE_PATHS) {
if (file === "pnpm-lock.yaml") continue;
await mkdir(path.dirname(path.join(root, file)), { recursive: true });
await writeFile(
path.join(root, file),
file === "artifacts/release/dependency-inventory.json"
? `${JSON.stringify({ lockfileSha256 })}\n`
: `${file}\n`,
);
}
await expect(createReleaseCandidateManifest(root)).rejects.toThrow(
/raw pnpm-lock digest mismatch/u,
);
} finally {
await rm(root, { recursive: true, force: true });
}
});
it("fails promotion when external provider evidence is absent", () => {
const result = evaluatePromotionEvidence({
expected: expectedProviderContext,
localStatus: "PASS",
vulnerabilityReport: null,
provenanceAttestation: null,
vulnerabilityTrust: null,
provenanceTrust: null,
nowEpochMs: () => NOW,
});
expect(result.status).toBe("FAIL_UNVERIFIED");
});
it("passes only signed provider evidence for the exact immutable candidate", () => {
const vulnerabilityKeys = generateKeyPairSync("ed25519");
const provenanceKeys = generateKeyPairSync("ed25519");
const { vulnerabilityReport, provenanceAttestation } = providerPair({
vulnerabilityKeys,
provenanceKeys,
});
const result = evaluatePromotionEvidence({
expected: expectedProviderContext,
localStatus: "PASS",
vulnerabilityReport,
provenanceAttestation,
vulnerabilityTrust: providerTrust(
"fixture-vulnerability-key",
vulnerabilityKeys.publicKey,
),
provenanceTrust: providerTrust(
"fixture-provenance-key",
provenanceKeys.publicKey,
),
nowEpochMs: () => NOW,
});
expect(result).toMatchObject({
status: "PASS",
vulnerabilityStatus: "PASS",
provenanceAttestationStatus: "PASS",
failures: [],
});
});
it("rejects correctly signed provider evidence for a different digest", () => {
const vulnerabilityKeys = generateKeyPairSync("ed25519");
const provenanceKeys = generateKeyPairSync("ed25519");
const wrongDistSha256 = "3".repeat(64);
const { vulnerabilityReport, provenanceAttestation } = providerPair({
vulnerabilityKeys,
provenanceKeys,
candidate: { ...expectedProviderContext.candidate, distSha256: wrongDistSha256 },
});
const result = evaluatePromotionEvidence({
expected: expectedProviderContext,
localStatus: "PASS",
vulnerabilityReport,
provenanceAttestation,
vulnerabilityTrust: providerTrust("fixture-vulnerability-key", vulnerabilityKeys.publicKey),
provenanceTrust: providerTrust("fixture-provenance-key", provenanceKeys.publicKey),
nowEpochMs: () => NOW,
});
expect(result.status).toBe("FAIL_UNVERIFIED");
expect(result.failures).toEqual(
expect.arrayContaining([
"vulnerability report candidate identity mismatch",
"provenance attestation candidate identity mismatch",
]),
);
});
it("rejects candidate bytes changed after provider attestation", () => {
const vulnerabilityKeys = generateKeyPairSync("ed25519");
const provenanceKeys = generateKeyPairSync("ed25519");
const { vulnerabilityReport, provenanceAttestation } = providerPair({
vulnerabilityKeys,
provenanceKeys,
});
const result = evaluatePromotionEvidence({
expected: {
...expectedProviderContext,
candidate: { ...expectedProviderContext.candidate, distSha256: "4".repeat(64) },
},
localStatus: "PASS",
vulnerabilityReport,
provenanceAttestation,
vulnerabilityTrust: providerTrust("fixture-vulnerability-key", vulnerabilityKeys.publicKey),
provenanceTrust: providerTrust("fixture-provenance-key", provenanceKeys.publicKey),
nowEpochMs: () => NOW,
});
expect(result.status).toBe("FAIL_UNVERIFIED");
expect(result.failures).toContain("vulnerability report candidate identity mismatch");
});
it("rejects Ed448 keys mislabeled as Ed25519 evidence", () => {
const vulnerabilityKeys = generateKeyPairSync("ed448");
const provenanceKeys = generateKeyPairSync("ed448");
const fakeFingerprint = `sha256:${"7".repeat(64)}`;
const { vulnerabilityReport, provenanceAttestation } = providerPair({
vulnerabilityKeys,
provenanceKeys,
vulnerabilityFingerprint: fakeFingerprint,
provenanceFingerprint: fakeFingerprint,
});
expect(
evaluatePromotionEvidence({
expected: expectedProviderContext,
localStatus: "PASS",
vulnerabilityReport,
provenanceAttestation,
vulnerabilityTrust: {
keyId: "fixture-vulnerability-key",
publicKey: vulnerabilityKeys.publicKey,
publicKeyFingerprint: fakeFingerprint,
},
provenanceTrust: {
keyId: "fixture-provenance-key",
publicKey: provenanceKeys.publicKey,
publicKeyFingerprint: fakeFingerprint,
},
nowEpochMs: () => NOW,
}).status,
).toBe("FAIL_UNVERIFIED");
});
it.each([
["empty", []],
["empty entry", [""]],
["blank entry", [" "]],
["absolute", ["/src"]],
["backslash", ["src\\file.ts"]],
["dot", ["."]],
["dotdot", [".."]],
["traversal", ["src/../docs"]],
["trailing slash", ["src/"]],
["mixed", ["src", 42]],
["duplicate", ["src", "src"]],
])("rejects %s secret-scan include paths", (_name, includedPaths) => {
expect(() => parseSecretScanIncludedPaths(includedPaths)).toThrow();
});
it("requires every configured include path to match the inventory", () => {
expect(
selectIncludedInventoryFiles(
["README.md", "src/app.ts"],
["src"],
),
).toEqual(["src/app.ts"]);
expect(() =>
selectIncludedInventoryFiles(
["README.md", "src/app.ts"],
["misspelled"],
),
).toThrow(/misspelled/u);
expect(
selectIncludedInventoryFiles(
["README.md", "src/app.ts"],
null,
),
).toEqual(["README.md", "src/app.ts"]);
});
it("rejects a crashed fixture scan and cannot reuse a stale repository artifact", async () => {
const cleaned: string[] = [];
await expect(
checkSecurityFixtures({
createTempDirectory: async () => "/tmp/fresh-security-fixture",
runScan: () => ({
status: 1,
signal: null,
stdout: "",
stderr: "Security scan found 3 blocking result(s).\n",
}),
readArtifact: async (artifactPath) => {
expect(artifactPath).toBe(
"/tmp/fresh-security-fixture/scan-fixture.sarif",
);
throw Object.assign(new Error("fresh artifact missing"), {
code: "ENOENT",
});
},
cleanup: async (directory) => {
cleaned.push(directory);
},
}),
).rejects.toThrow(/fresh artifact missing/u);
expect(cleaned).toEqual(["/tmp/fresh-security-fixture"]);
await expect(
checkSecurityFixtures({
createTempDirectory: async () => "/tmp/fresh-security-fixture",
runScan: () => ({
status: null,
signal: "SIGTERM",
stdout: "",
stderr: "Security scan found 3 blocking result(s).\n",
}),
readArtifact: async () => "{}",
cleanup: async () => undefined,
}),
).rejects.toThrow(/did not fail exactly/u);
});
it("wires the exact security fixture checker as a passing CI gate", async () => {
const contract = await loadCiGateContract(process.cwd(), {
mode: process.env.CI_CONTRACT_MODE === "removal-fixture"
? "removal-fixture"
: "canonical",
});
const index = indexCiGateContract(contract);
const securityGate = index.gates.get("FE-GATE-013");
expect(securityGate).toBeDefined();
const commands = securityGate!.commandIds.map((commandId) =>
index.commands.get(commandId),
);
expect(commands).toContainEqual(
expect.objectContaining({
script: "check:security:fixtures",
expect: "pass",
}),
);
expect(commands).not.toEqual(
expect.arrayContaining([
expect.objectContaining({ script: "scan:security:fixture" }),
]),
);
const evidence = securityGate!.evidenceArtifactIds.map(
(artifactId) => index.artifacts.get(artifactId)?.path,
);
expect(evidence).not.toContain(
"artifacts/security/scan-fixture.sarif",
);
});
it("uses one fail-closed repository inventory for provenance and secret scanning", async () => {
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");
}
});
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"],
]);
});
// A removal fixture deletes some of these inputs on purpose — removing the
// browser file/storage capability takes the whole browser-capability harness
// with it — and prunes them from its own policy. This is a claim about the
// full repository, so it does not describe a deliberately reduced one.
it.skipIf(isReducedCiContractRun())("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);
});
});