fix: harden provider and promotion evidence
This commit is contained in:
@@ -151,16 +151,19 @@ describe("CI gate contract", () => {
|
||||
["candidate output identity drift", (value: Record<string, any>) => { const job = value.jobs.find((candidate: any) => candidate.id === "immutable_build"); job.steps.find((step: any) => step.kind === "archive-candidate").archiveOutputName = "renamed"; }, /candidate output identity drift/i],
|
||||
["stage cycle", (value: Record<string, any>) => (value.stages[0].needs = ["release"]), /stage dependency cycle/i],
|
||||
["provider adapter target drift", (value: Record<string, any>) => (value.providerAdapter = "package.json"), /canonical generated workflow/i],
|
||||
["workflow root extraction", (value: Record<string, any>) => (value.jobs.find((candidate: any) => candidate.id === "promotion").steps.find((step: any) => step.kind === "extract").targetRoot = ".."), /unsafe workflow path/i],
|
||||
["promotion standalone extraction", (value: Record<string, any>) => value.jobs.find((candidate: any) => candidate.id === "promotion").steps.splice(6, 0, { kind: "extract", archivePath: ".release/candidate/candidate.tar.gz", targetRoot: ".release/verified-candidate" }), /step kind extract is forbidden|job step sequence drift/i],
|
||||
["normalized upload root", (value: Record<string, any>) => (value.jobs[0].steps.find((step: any) => step.kind === "upload").paths = ["foo/.."]), /unsafe workflow path/i],
|
||||
["immutable archive field drift", (value: Record<string, any>) => (value.jobs.find((candidate: any) => candidate.id === "immutable_build").steps.find((step: any) => step.kind === "archive-candidate").archivePath = ".release/other.tar.gz"), /candidate output identity drift|archive and upload fields must remain linked/i],
|
||||
["provider role drift", (value: Record<string, any>) => (value.jobs.find((candidate: any) => candidate.id === "vulnerability_provider").steps.find((step: any) => step.kind === "run-provider").provider = "provenance"), /provider archive, extraction, evidence, and upload fields must remain linked/i],
|
||||
["provider archive SHA environment drift", (value: Record<string, any>) => (value.jobs.find((candidate: any) => candidate.id === "vulnerability_provider").environment.find((entry: any) => entry.name === "CANDIDATE_ARCHIVE_SHA256").value = "0".repeat(64)), /job environment binding drift/i],
|
||||
["promotion transfer swap", (value: Record<string, any>) => (value.jobs.find((candidate: any) => candidate.id === "promotion").steps.find((step: any) => step.kind === "download").transferId = "vulnerability-provider-evidence"), /promotion download and extraction fields must remain linked|duplicate.*download/i],
|
||||
["promotion transfer swap", (value: Record<string, any>) => (value.jobs.find((candidate: any) => candidate.id === "promotion").steps.find((step: any) => step.kind === "download").transferId = "vulnerability-provider-evidence"), /promotion download fields.*remain linked|duplicate.*download/i],
|
||||
["raw provider upload", (value: Record<string, any>) => (value.jobs.find((candidate: any) => candidate.id === "vulnerability_provider").steps.find((step: any) => step.kind === "upload").paths = ["provider-evidence/untrusted/vulnerability-report.json"]), /provider archive, extraction, evidence, and upload fields must remain linked/i],
|
||||
["intervening promotion step", (value: Record<string, any>) => value.jobs.find((candidate: any) => candidate.id === "promotion").steps.splice(-1, 0, { kind: "frozen-install" }), /promotion verification and upload must be immediately adjacent/i],
|
||||
["promotion upload path drift", (value: Record<string, any>) => (value.jobs.find((candidate: any) => candidate.id === "promotion").steps.at(-1).paths[0] = ".release/promoted-staging/replaced.tar.gz"), /exact five typed paths/i],
|
||||
["always promotion upload", (value: Record<string, any>) => (value.jobs.find((candidate: any) => candidate.id === "promotion").steps.at(-1).always = true), /promotion upload must not use always/i],
|
||||
["intervening promotion step", (value: Record<string, any>) => { const steps = value.jobs.find((candidate: any) => candidate.id === "promotion").steps; steps.splice(steps.findIndex((step: any) => step.kind === "upload"), 0, { kind: "frozen-install" }); }, /promotion verification and upload must be immediately adjacent/i],
|
||||
["promotion upload path drift", (value: Record<string, any>) => (value.jobs.find((candidate: any) => candidate.id === "promotion").steps.find((step: any) => step.kind === "upload").paths[0] = ".release/promoted-staging/replaced.tar.gz"), /exact five typed paths/i],
|
||||
["always promotion upload", (value: Record<string, any>) => (value.jobs.find((candidate: any) => candidate.id === "promotion").steps.find((step: any) => step.kind === "upload").always = true), /promotion upload must not use always/i],
|
||||
["provider nonce output step drift", (value: Record<string, any>) => (value.jobs.find((candidate: any) => candidate.id === "vulnerability_provider").steps.find((step: any) => step.kind === "run-provider").stepId = "renamed"), /provider.*linked|step identity/i],
|
||||
["promotion nonce binding drift", (value: Record<string, any>) => (value.jobs.find((candidate: any) => candidate.id === "promotion").environment.find((entry: any) => entry.name === "VULNERABILITY_INVOCATION_NONCE").value = "5".repeat(64)), /job environment binding drift/i],
|
||||
["missing promotion cleanup", (value: Record<string, any>) => { const job = value.jobs.find((candidate: any) => candidate.id === "promotion"); job.steps = job.steps.filter((step: any) => step.kind !== "cleanup-promotion"); }, /job step sequence drift|cleanup/i],
|
||||
])("rejects semantic mutation: %s", async (_name, mutate, diagnostic) => {
|
||||
const contract = await loadCiGateContract(process.cwd());
|
||||
const candidate = JSON.parse(JSON.stringify(contract)) as Record<string, any>;
|
||||
@@ -448,10 +451,25 @@ describe("CI workflow generation", () => {
|
||||
).toHaveLength(9);
|
||||
expect(first).not.toMatch(/corepack pnpm install --frozen-lockfile$/mu);
|
||||
expect(first).toContain("verify-ci-candidate-archive.ts --archive");
|
||||
expect(first).toContain("--extract-to");
|
||||
expect(first).not.toContain("--extract-to");
|
||||
expect(first).not.toMatch(/\btar\s+[^\n]*--extract/u);
|
||||
expect(first).toContain("node scripts/stage-verified-promotion.ts");
|
||||
expect(first).toContain(".release/promoted-staging/release-candidate.tar.gz");
|
||||
expect(first).toContain("outputs:\n invocation_nonce: ${{ steps.supervise_vulnerability.outputs.invocation_nonce }}");
|
||||
expect(first).toContain("outputs:\n invocation_nonce: ${{ steps.supervise_provenance.outputs.invocation_nonce }}");
|
||||
expect(first).toContain('VULNERABILITY_INVOCATION_NONCE: "${{ needs.vulnerability_provider.outputs.invocation_nonce }}"');
|
||||
expect(first).toContain('PROVENANCE_INVOCATION_NONCE: "${{ needs.provenance_provider.outputs.invocation_nonce }}"');
|
||||
expect(first).toContain("${{ steps.finalize.outputs.staging_root }}/release-candidate.tar.gz");
|
||||
expect(first).not.toContain(".release/promoted-staging");
|
||||
const finalizerIndex = first.indexOf("node scripts/stage-verified-promotion.ts");
|
||||
const promotedUploadIndex = first.indexOf("Upload promoted release");
|
||||
const cleanupStepIndex = first.indexOf("- name: Always remove private promotion staging");
|
||||
const cleanupIndex = first.indexOf("node scripts/cleanup-verified-promotion.ts");
|
||||
expect(finalizerIndex).toBeGreaterThan(0);
|
||||
expect(promotedUploadIndex).toBeGreaterThan(finalizerIndex);
|
||||
expect(cleanupStepIndex).toBeGreaterThan(promotedUploadIndex);
|
||||
expect(cleanupIndex).toBeGreaterThan(cleanupStepIndex);
|
||||
expect(first.slice(promotedUploadIndex, cleanupStepIndex)).not.toContain("if: always()");
|
||||
expect(first.slice(cleanupStepIndex, cleanupIndex)).toContain("if: always()");
|
||||
const actionUses = [...first.matchAll(/^\s+-?\s*uses: (.+)$/gmu)].map((match) => match[1]);
|
||||
expect(actionUses).toHaveLength(32);
|
||||
expect(new Set(actionUses)).toEqual(
|
||||
|
||||
Reference in New Issue
Block a user