refactor: 프론트엔드 리펙토링
This commit is contained in:
@@ -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,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user