refactor: 프론트엔드 리펙토링

This commit is contained in:
donghyeon-ka
2026-09-18 22:05:42 +09:00
parent 5cc41467ae
commit ec7f20e2ee
100 changed files with 6005 additions and 2867 deletions
@@ -0,0 +1,708 @@
import { generateKeyPairSync } from "node:crypto";
import { mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import { EventEmitter } from "node:events";
import { describe, expect, it } from "vitest";
import {
evaluatePromotionEvidence,
providerPublicKeyFingerprint,
validateProviderEvidence,
} from "../../scripts/lib/provider-evidence.ts";
import { readProviderTrust } from "../../scripts/lib/provider-trust.ts";
import { superviseProviderEvidence } from "../../scripts/lib/provider-supervisor.ts";
import { runProviderProcess } from "../../scripts/lib/provider-process-runner.ts";
import { runStageVerifiedPromotionCli } from "../../scripts/lib/stage-verified-promotion-cli.ts";
import type { ReleaseCandidateManifest } from "../../scripts/lib/release-candidate.ts";
import {
digest,
providerExpectedContext,
providerUnsigned,
signedProviderV2,
trust,
} from "./security-followup-fixture.ts";
describe("security provider evidence contracts", () => {
it("accepts signed provider v2 evidence only for the exact run, source, archive, and nonce", () => {
const now = Date.parse("2026-08-02T01:00:00.000Z");
const vulnerabilityKeys = generateKeyPairSync("ed25519");
const provenanceKeys = generateKeyPairSync("ed25519");
const expected = providerExpectedContext();
const vulnerability = signedProviderV2(
{
source: expected.source,
candidate: expected.candidate,
schemaVersion: 2,
evidenceType: "vulnerability-report",
provider: "fixture-vulnerability",
issuedAt: "2026-08-02T01:00:00.000Z",
expiresAt: "2026-08-02T02:00:00.000Z",
run: { ...expected.run, invocationNonce: "1".repeat(64) },
secretScanAttestation: expected.secretScanAttestation,
findings: [],
},
"vulnerability-key",
vulnerabilityKeys.publicKey,
vulnerabilityKeys.privateKey,
);
const provenance = signedProviderV2(
{
source: expected.source,
candidate: expected.candidate,
schemaVersion: 2,
evidenceType: "provenance-attestation",
provider: "fixture-provenance",
signer: "fixture-workload",
issuedAt: "2026-08-02T01:00:00.000Z",
expiresAt: "2026-08-02T02:00:00.000Z",
run: { ...expected.run, invocationNonce: "2".repeat(64) },
subject: { name: "dist", digest: { sha256: expected.candidate.distSha256 } },
},
"provenance-key",
provenanceKeys.publicKey,
provenanceKeys.privateKey,
);
const result = evaluatePromotionEvidence({
expected: {
...expected,
vulnerabilityInvocationNonce: "1".repeat(64),
provenanceInvocationNonce: "2".repeat(64),
},
localStatus: "PASS",
vulnerabilityReport: vulnerability,
provenanceAttestation: provenance,
vulnerabilityTrust: trust("vulnerability-key", vulnerabilityKeys.publicKey),
provenanceTrust: trust("provenance-key", provenanceKeys.publicKey),
nowEpochMs: () => now,
});
expect(result).toEqual({
status: "PASS",
vulnerabilityStatus: "PASS",
provenanceAttestationStatus: "PASS",
failures: [],
});
const replayed = evaluatePromotionEvidence({
expected: {
...expected,
run: { id: expected.run.id, attempt: 2 },
vulnerabilityInvocationNonce: "1".repeat(64),
provenanceInvocationNonce: "2".repeat(64),
},
localStatus: "PASS",
vulnerabilityReport: vulnerability,
provenanceAttestation: provenance,
vulnerabilityTrust: trust("vulnerability-key", vulnerabilityKeys.publicKey),
provenanceTrust: trust("provenance-key", provenanceKeys.publicKey),
nowEpochMs: () => now,
});
expect(replayed.status).toBe("FAIL_UNVERIFIED");
expect(replayed.failures).toEqual(
expect.arrayContaining([
"vulnerability report run identity mismatch",
"provenance attestation run identity mismatch",
]),
);
});
it("rejects a signed vulnerability PASS when the captured SARIF attestation differs", () => {
const keys = generateKeyPairSync("ed25519");
const expected = providerExpectedContext();
const secretScanAttestation = {
status: "PASS" as const,
localEvidenceAssessmentSha256: digest("assessment"),
sourceSetSha256: expected.source.sourceSetSha256,
policySha256: digest("secret policy"),
sarifSha256: digest("real sarif"),
scanInputSha256: digest("scan input"),
};
const report = signedProviderV2(
{
schemaVersion: 2,
evidenceType: "vulnerability-report",
provider: "fixture-vulnerability",
issuedAt: "2026-08-02T01:00:00.000Z",
expiresAt: "2026-08-02T02:00:00.000Z",
run: { ...expected.run, invocationNonce: "1".repeat(64) },
source: expected.source,
candidate: expected.candidate,
secretScanAttestation,
findings: [],
},
"vulnerability-key",
keys.publicKey,
keys.privateKey,
);
const validated = validateProviderEvidence({
kind: "vulnerability",
value: report,
expected: {
...expected,
vulnerabilityInvocationNonce: "1".repeat(64),
provenanceInvocationNonce: "2".repeat(64),
secretScanAttestation: {
...secretScanAttestation,
sarifSha256: digest("forged empty sarif"),
},
},
trust: trust("vulnerability-key", keys.publicKey),
nowEpochMs: () => Date.parse("2026-08-02T01:00:00.000Z"),
});
expect(validated.status).toBe("FAIL_UNVERIFIED");
expect(validated.failures).toContain(
"vulnerability report secret scan attestation mismatch",
);
const forged = structuredClone(report);
forged.secretScanAttestation.sarifSha256 = digest("forged empty sarif");
const forgedValidation = validateProviderEvidence({
kind: "vulnerability",
value: forged,
expected: {
...expected,
vulnerabilityInvocationNonce: "1".repeat(64),
provenanceInvocationNonce: "2".repeat(64),
secretScanAttestation: forged.secretScanAttestation,
},
trust: trust("vulnerability-key", keys.publicKey),
nowEpochMs: () => Date.parse("2026-08-02T01:00:00.000Z"),
});
expect(forgedValidation.failures).toContain(
"vulnerability report signature verification failed",
);
const provenanceKeys = generateKeyPairSync("ed25519");
const provenance = signedProviderV2(
{
schemaVersion: 2,
evidenceType: "provenance-attestation",
provider: "fixture-provenance",
signer: "fixture-workload",
issuedAt: "2026-08-02T01:00:00.000Z",
expiresAt: "2026-08-02T02:00:00.000Z",
run: { ...expected.run, invocationNonce: "2".repeat(64) },
source: expected.source,
candidate: expected.candidate,
subject: { name: "dist", digest: { sha256: expected.candidate.distSha256 } },
},
"provenance-key",
provenanceKeys.publicKey,
provenanceKeys.privateKey,
);
const evaluated = evaluatePromotionEvidence({
expected: {
...expected,
vulnerabilityInvocationNonce: "1".repeat(64),
provenanceInvocationNonce: "2".repeat(64),
secretScanAttestation: {
...secretScanAttestation,
sarifSha256: digest("forged empty sarif"),
},
},
localStatus: "PASS",
vulnerabilityReport: report,
provenanceAttestation: provenance,
vulnerabilityTrust: trust("vulnerability-key", keys.publicKey),
provenanceTrust: trust("provenance-key", provenanceKeys.publicKey),
nowEpochMs: () => Date.parse("2026-08-02T01:00:00.000Z"),
});
expect(evaluated.vulnerabilityStatus).toBe("FAIL_UNVERIFIED");
expect(evaluated.failures).toContain(
"vulnerability report secret scan attestation mismatch",
);
});
it.each(["vulnerability", "provenance"] as const)(
"rejects correctly re-signed %s v2 context/time/replay drift",
(kind) => {
const now = Date.parse("2026-08-02T01:00:00.000Z");
const vulnerabilityKeys = generateKeyPairSync("ed25519");
const provenanceKeys = generateKeyPairSync("ed25519");
const expected = providerExpectedContext();
const baseVulnerability = providerUnsigned("vulnerability", expected);
const baseProvenance = providerUnsigned("provenance", expected);
const validVulnerability = signedProviderV2(
baseVulnerability,
"vulnerability-key",
vulnerabilityKeys.publicKey,
vulnerabilityKeys.privateKey,
);
const validProvenance = signedProviderV2(
baseProvenance,
"provenance-key",
provenanceKeys.publicKey,
provenanceKeys.privateKey,
);
const rawCases: Array<readonly [
string,
(value: Record<string, any>) => Record<string, any>,
RegExp,
]> = [
["schema v1", (value) => ({ ...value, schemaVersion: 1 }), /missing or invalid/u],
[
"evidence type",
(value) => ({
...value,
evidenceType:
kind === "vulnerability"
? "provenance-attestation"
: "vulnerability-report",
}),
/missing or invalid/u,
],
...(["archiveSha256", "bundleSha256", "distSha256", "lockfileSha256"] as const).map(
(field) => [
`candidate ${field}`,
(value: Record<string, any>) => ({
...value,
candidate: { ...value.candidate, [field]: "f".repeat(64) },
...(kind === "provenance" && field === "distSha256"
? {
subject: {
name: "dist",
digest: { sha256: "f".repeat(64) },
},
}
: {}),
}),
/candidate identity|subject dist/u,
] as const,
),
[
"different archive with same dist and lockfile",
(value) => ({
...value,
candidate: { ...value.candidate, archiveSha256: "e".repeat(64) },
}),
/candidate identity/u,
],
[
"source revision",
(value) => ({ ...value, source: { ...value.source, revision: "c".repeat(40) } }),
/source identity/u,
],
[
"source set",
(value) => ({ ...value, source: { ...value.source, sourceSetSha256: "c".repeat(64) } }),
/source identity/u,
],
[
"run id",
(value) => ({ ...value, run: { ...value.run, id: "other-run" } }),
/run identity/u,
],
[
"run attempt replay",
(value) => ({ ...value, run: { ...value.run, attempt: 2 } }),
/run identity/u,
],
[
"different nonce",
(value) => ({ ...value, run: { ...value.run, invocationNonce: "3".repeat(64) } }),
/invocation nonce/u,
],
[
"missing nonce",
(value) => {
const run = { ...value.run };
delete run.invocationNonce;
return { ...value, run };
},
/missing or invalid/u,
],
[
"uppercase nonce",
(value) => ({ ...value, run: { ...value.run, invocationNonce: "A".repeat(64) } }),
/missing or invalid/u,
],
[
"short nonce",
(value) => ({ ...value, run: { ...value.run, invocationNonce: "1".repeat(62) } }),
/missing or invalid/u,
],
[
"issued future boundary",
(value) => ({ ...value, issuedAt: "2026-08-02T01:05:00.001Z" }),
/future skew/u,
],
[
"expiry equality",
(value) => ({ ...value, expiresAt: "2026-08-02T01:00:00.000Z" }),
/expired/u,
],
[
"expiry past",
(value) => ({ ...value, expiresAt: "2026-08-02T00:59:59.999Z" }),
/expired/u,
],
[
"zero lifetime",
(value) => ({
...value,
issuedAt: "2026-08-02T01:01:00.000Z",
expiresAt: "2026-08-02T01:01:00.000Z",
}),
/not positive/u,
],
[
"negative lifetime",
(value) => ({
...value,
issuedAt: "2026-08-02T01:02:00.000Z",
expiresAt: "2026-08-02T01:01:59.999Z",
}),
/not positive/u,
],
[
"lifetime above two hours",
(value) => ({
...value,
issuedAt: "2026-08-02T01:00:00.000Z",
expiresAt: "2026-08-02T03:00:00.001Z",
}),
/exceeds two hours/u,
],
[
"wrong fingerprint",
(value) => ({
...value,
signature: {
...value.signature,
publicKeyFingerprint: `sha256:${"d".repeat(64)}`,
},
}),
/trust identity/u,
],
];
const cases = rawCases.map(([name, mutate, failure]) => ({
name,
mutate,
failure,
}));
for (const testCase of cases) {
const base = kind === "vulnerability" ? baseVulnerability : baseProvenance;
const mutated = testCase.mutate(structuredClone(base));
const resigned = signedProviderV2(
mutated,
kind === "vulnerability" ? "vulnerability-key" : "provenance-key",
kind === "vulnerability" ? vulnerabilityKeys.publicKey : provenanceKeys.publicKey,
kind === "vulnerability" ? vulnerabilityKeys.privateKey : provenanceKeys.privateKey,
"signature" in mutated && mutated.signature?.publicKeyFingerprint
? mutated.signature.publicKeyFingerprint
: undefined,
);
const result = evaluatePromotionEvidence({
expected: {
...expected,
vulnerabilityInvocationNonce: "1".repeat(64),
provenanceInvocationNonce: "2".repeat(64),
},
localStatus: "PASS",
vulnerabilityReport:
kind === "vulnerability" ? resigned : validVulnerability,
provenanceAttestation:
kind === "provenance" ? resigned : validProvenance,
vulnerabilityTrust: trust("vulnerability-key", vulnerabilityKeys.publicKey),
provenanceTrust: trust("provenance-key", provenanceKeys.publicKey),
nowEpochMs: () => now,
});
expect(result.status, testCase.name).toBe("FAIL_UNVERIFIED");
expect(result.failures.join("\n"), testCase.name).toMatch(testCase.failure);
}
},
);
it("canonicalizes provider fingerprints from DER SPKI across PEM wrapping and rejects Ed448", async () => {
const root = await mkdtemp(path.join(tmpdir(), "provider-fingerprint-"));
try {
const ed25519 = generateKeyPairSync("ed25519").publicKey;
const pem = ed25519.export({ type: "spki", format: "pem" }).toString();
const body = pem.replace(/-----[^-]+-----|\s/gu, "");
const wrapped = (width: number) =>
`-----BEGIN PUBLIC KEY-----\n${body.match(new RegExp(`.{1,${width}}`, "gu"))!.join("\n")}\n-----END PUBLIC KEY-----\n`;
await writeFile(path.join(root, "a.pem"), wrapped(64));
await writeFile(path.join(root, "b.pem"), wrapped(32));
const first = await readProviderTrust(root, "a.pem", "fixture-key");
const second = await readProviderTrust(root, "b.pem", "fixture-key");
expect(first?.publicKeyFingerprint).toBe(providerPublicKeyFingerprint(ed25519));
expect(second?.publicKeyFingerprint).toBe(first?.publicKeyFingerprint);
const ed448 = generateKeyPairSync("ed448").publicKey;
await writeFile(root + "/ed448.pem", ed448.export({ type: "spki", format: "pem" }));
await expect(readProviderTrust(root, "ed448.pem", "fixture-key")).resolves.toBeNull();
expect(() => providerPublicKeyFingerprint(ed448)).toThrow(/must be Ed25519/u);
} finally {
await rm(root, { recursive: true, force: true });
}
});
it("captures the downloaded archive pathname exactly once in the provider supervisor", async () => {
const keys = generateKeyPairSync("ed25519");
const expected = providerExpectedContext();
let captureCount = 0;
let receivedEnvironment: Readonly<Record<string, string>> | undefined;
const manifest: ReleaseCandidateManifest = {
schemaVersion: 1,
distSha256: expected.candidate.distSha256,
lockfileSha256: expected.candidate.lockfileSha256,
bundleSha256: expected.candidate.bundleSha256,
files: [{ path: "pnpm-lock.yaml", bytes: 1, sha256: expected.candidate.lockfileSha256 }],
};
const result = await superviseProviderEvidence(
{
kind: "vulnerability",
archivePath: "/downloads/candidate.tar.gz",
expectedArchiveSha256: expected.candidate.archiveSha256,
expectedRun: {
id: expected.run.id,
attempt: expected.run.attempt,
sourceRevision: expected.source.revision,
},
trust: trust("vulnerability-key", keys.publicKey),
executeProvider: async ({ environment }) => {
receivedEnvironment = environment;
},
captureReport: async () => Buffer.from("{}\n"),
},
{
captureArchive: async (input) => {
captureCount += 1;
expect(input).toEqual({
archivePath: "/downloads/candidate.tar.gz",
expectedSha256: expected.candidate.archiveSha256,
});
return {
bytes: Buffer.from("captured archive"),
archiveSha256: expected.candidate.archiveSha256,
};
},
withVerifiedCandidate: (async (input: any) =>
input.verify({ extractionRoot: "/captured/extraction", manifest })) as any,
verifyLocalEvidence: async () => ({
status: "PASS",
identity: {
sourceRevision: expected.source.revision,
sourceSetSha256: expected.source.sourceSetSha256,
assessmentSha256: digest("assessment"),
secretScan: {
policySha256: digest("provider secret policy"),
sarifSha256: digest("provider secret sarif"),
scanInputSha256: digest("provider secret input"),
},
},
failures: [],
}),
validateUpload: (async (input: any) => {
expect("archivePath" in input).toBe(false);
return { sealed: true };
}) as any,
randomBytes: () => Buffer.alloc(32, 0x11),
nowEpochMs: () => Date.parse("2026-08-02T01:00:00.000Z"),
},
);
expect(captureCount).toBe(1);
expect(receivedEnvironment).toEqual(
expect.objectContaining({
PROVIDER_EVIDENCE_SCHEMA_VERSION: "2",
PROVIDER_INVOCATION_NONCE: "11".repeat(32),
PROVIDER_ISSUED_AT: "2026-08-02T01:00:00.000Z",
PROVIDER_EXPIRES_AT: "2026-08-02T02:00:00.000Z",
CI_RUN_ID: expected.run.id,
CI_RUN_ATTEMPT: "1",
SOURCE_REVISION: expected.source.revision,
CANDIDATE_ARCHIVE_SHA256: expected.candidate.archiveSha256,
}),
);
expect(result.evidence).toEqual({ sealed: true });
});
it("samples provider freshness after report capture instead of reusing issuance time", async () => {
const keys = generateKeyPairSync("ed25519");
const expected = providerExpectedContext();
const manifest: ReleaseCandidateManifest = {
schemaVersion: 1,
distSha256: expected.candidate.distSha256,
lockfileSha256: expected.candidate.lockfileSha256,
bundleSha256: expected.candidate.bundleSha256,
files: [{ path: "pnpm-lock.yaml", bytes: 1, sha256: expected.candidate.lockfileSha256 }],
};
const issuedSample = Date.parse("2026-08-02T01:00:00.000Z");
const validationSample = Date.parse("2026-08-02T02:00:00.001Z");
const samples = [issuedSample, validationSample];
let issuedAt = "";
await expect(
superviseProviderEvidence(
{
kind: "vulnerability",
archivePath: "/downloads/candidate.tar.gz",
expectedArchiveSha256: expected.candidate.archiveSha256,
expectedRun: {
id: expected.run.id,
attempt: expected.run.attempt,
sourceRevision: expected.source.revision,
},
trust: trust("vulnerability-key", keys.publicKey),
executeProvider: async ({ environment }) => {
issuedAt = environment.PROVIDER_ISSUED_AT!;
},
captureReport: async () => Buffer.from("{}\n"),
},
{
captureArchive: async () => ({
bytes: Buffer.from("captured archive"),
archiveSha256: expected.candidate.archiveSha256,
}),
withVerifiedCandidate: (async (input: any) =>
input.verify({ extractionRoot: "/captured/extraction", manifest })) as any,
verifyLocalEvidence: async () => ({
status: "PASS",
identity: {
sourceRevision: expected.source.revision,
sourceSetSha256: expected.source.sourceSetSha256,
assessmentSha256: digest("assessment"),
secretScan: {
policySha256: digest("provider secret policy"),
sarifSha256: digest("provider secret sarif"),
scanInputSha256: digest("provider secret input"),
},
},
failures: [],
}),
validateUpload: (async (input: any) => {
expect(input.nowEpochMs()).toBe(validationSample);
throw new Error("provider report expired during execution");
}) as any,
randomBytes: () => Buffer.alloc(32, 0x33),
nowEpochMs: () => samples.shift()!,
},
),
).rejects.toThrow(/expired during execution/u);
expect(issuedAt).toBe("2026-08-02T01:00:00.000Z");
});
it("kills a timed-out provider but settles only after the child closes", async () => {
const child = new EventEmitter() as EventEmitter & {
kill(signal: NodeJS.Signals): boolean;
};
let killedWith: NodeJS.Signals | undefined;
child.kill = (signal) => {
killedWith = signal;
return true;
};
let fireTimeout: (() => void) | undefined;
let settled = false;
const running = runProviderProcess(
{ executable: "/usr/bin/bwrap", arguments: [], environment: {}, timeoutMs: 1 },
{
spawnChild: () => child as any,
setTimer: (callback) => {
fireTimeout = callback;
return 1 as any;
},
clearTimer: () => undefined,
},
).finally(() => {
settled = true;
});
fireTimeout?.();
await Promise.resolve();
expect(killedWith).toBe("SIGKILL");
expect(settled).toBe(false);
child.emit("close", null, "SIGKILL");
await expect(running).rejects.toThrow(/timed out/u);
expect(settled).toBe(true);
});
it("captures process-group kill errors, attempts child fallback, and settles after close", async () => {
const child = new EventEmitter() as EventEmitter & {
pid: number;
kill(signal: NodeJS.Signals): boolean;
};
child.pid = 12_346;
let fallbackSignal: NodeJS.Signals | undefined;
child.kill = (signal) => {
fallbackSignal = signal;
return true;
};
let fireTimeout: (() => void) | undefined;
const running = runProviderProcess(
{ executable: "/usr/bin/bwrap", arguments: [], environment: {}, timeoutMs: 1 },
{
spawnChild: () => child as any,
setTimer: (callback) => {
fireTimeout = callback;
return 1 as any;
},
clearTimer: () => undefined,
killProcessGroup: () => {
throw Object.assign(new Error("group kill denied"), { code: "EPERM" });
},
},
);
expect(() => fireTimeout?.()).not.toThrow();
expect(fallbackSignal).toBe("SIGKILL");
child.emit("close", null, "SIGKILL");
await expect(running).rejects.toThrow(/timed out.*kill failed.*close/u);
});
it.each(["open failure", "partial write failure"])(
"cleans finalized staging from memory when GITHUB_OUTPUT has a %s",
async (failureKind) => {
const finalized = {
stagingRoot: "/runner/promotion-run-1-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
cleanupToken: "promotion-run-1-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
runnerTempIdentity: { dev: 10, ino: 20 },
stagingIdentity: { dev: 30, ino: 40 },
files: [],
} as const;
let cleanupInput: unknown;
let appendCalls = 0;
const environment = {
CANDIDATE_ARCHIVE_PATH: "candidate.tar.gz",
CANDIDATE_ARCHIVE_SHA256: "a".repeat(64),
VULNERABILITY_REPORT_PATH: "vulnerability.json",
PROVENANCE_ATTESTATION_PATH: "provenance.json",
VULNERABILITY_PUBLIC_KEY_PATH: "vulnerability.pem",
VULNERABILITY_KEY_ID: "vulnerability-key",
PROVENANCE_PUBLIC_KEY_PATH: "provenance.pem",
PROVENANCE_KEY_ID: "provenance-key",
CI_RUN_ID: "run",
CI_RUN_ATTEMPT: "1",
VITE_COMMIT_SHA: "b".repeat(40),
VULNERABILITY_INVOCATION_NONCE: "c".repeat(64),
PROVENANCE_INVOCATION_NONCE: "d".repeat(64),
RUNNER_TEMP: "/runner",
GITHUB_OUTPUT: "/runner/github-output",
};
await expect(
runStageVerifiedPromotionCli(environment, {
cwd: () => "/workspace",
finalize: async () => finalized as any,
appendOutput: async () => {
appendCalls += 1;
if (failureKind === "partial write failure") {
// The output sink accepted an unspecified prefix before rejecting.
}
throw new Error(failureKind);
},
cleanup: async (input) => {
cleanupInput = input;
},
writeStdout: () => undefined,
}),
).rejects.toThrow(new RegExp(failureKind, "u"));
expect(appendCalls).toBe(1);
expect(cleanupInput).toEqual({
runnerTempRoot: "/runner",
stagingRoot: finalized.stagingRoot,
cleanupToken: finalized.cleanupToken,
runnerTempIdentity: finalized.runnerTempIdentity,
stagingIdentity: finalized.stagingIdentity,
});
},
);
});