Files
OmniRoute/tests/unit/check-workflows-provenance-runner.test.ts
diegosouzapw fb7445eaa3 test(check): escape the runs-on fixture with JSON.stringify, not a quote-only replace
CodeQL js/incomplete-sanitization (#888): the hand-rolled replace only escaped
double quotes, so a backslash in the fixture would have produced a malformed YAML
scalar. JSON.stringify covers every escape the double-quoted YAML scalar needs.
2026-08-28 17:29:04 -03:00

146 lines
5.0 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
import { readFileSync, readdirSync } from "node:fs";
import { join } from "node:path";
import {
classifyRunsOn,
findProvenanceOnSelfHosted,
} from "../../scripts/check/lib/provenanceRunner.mjs";
/**
* v3.8.50, 10th publish attempt, 76 minutes in — after the tag, the GitHub
* Release and the Docker images were already public:
*
* 422 Unprocessable Entity - Error verifying sigstore provenance bundle:
* Unsupported GitHub Actions runner environment: "self-hosted".
*
* `USE_VPS_RUNNER` had routed the publish job to the .113 pool on 2026-08-02;
* no release happened between 07-30 and 08-28, so nothing surfaced it. The
* pairing is pure text, so it must fail the workflow lint on the PR that
* introduces it.
*/
const ROOT = join(import.meta.dirname, "../..");
const WORKFLOWS = join(ROOT, ".github/workflows");
// The exact runs-on expression npm-publish.yml used when it broke.
const VPS_EXPR =
"${{ (vars.USE_VPS_RUNNER == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)) && fromJSON('[\"self-hosted\",\"omni-release\"]') || 'ubuntu-latest' }}";
function workflow(runsOn: string, run: string, extra = ""): string {
return [
"name: t",
"on: push",
"jobs:",
" publish:",
` runs-on: ${runsOn}`,
extra,
" steps:",
" - name: upload",
` run: ${run}`,
"",
].join("\n");
}
test("classifyRunsOn: literal, array, object-with-labels and the fromJSON expression are self-hosted", () => {
assert.equal(classifyRunsOn("self-hosted"), "self-hosted");
assert.equal(classifyRunsOn(["self-hosted", "omni-release"]), "self-hosted");
assert.equal(classifyRunsOn({ group: "Default", labels: ["self-hosted"] }), "self-hosted");
assert.equal(classifyRunsOn(VPS_EXPR), "self-hosted");
});
test("classifyRunsOn: hosted labels are hosted, opaque expressions are unknown (never guessed)", () => {
assert.equal(classifyRunsOn("ubuntu-latest"), "hosted");
assert.equal(classifyRunsOn(["ubuntu-latest"]), "hosted");
assert.equal(classifyRunsOn("${{ matrix.os }}"), "unknown");
assert.equal(classifyRunsOn(undefined), "unknown");
});
test("flags --provenance inside a job routed to the self-hosted pool", () => {
const found = findProvenanceOnSelfHosted(
workflow(
JSON.stringify(VPS_EXPR),
'npm stage publish --provenance --access public --tag "$TAG"'
),
"npm-publish.yml"
);
assert.deepEqual(found, [{ file: "npm-publish.yml", job: "publish", step: "upload" }]);
});
test("also catches the literal label and the --provenance-file form", () => {
assert.equal(
findProvenanceOnSelfHosted(workflow("self-hosted", "npm publish --provenance")).length,
1
);
assert.equal(
findProvenanceOnSelfHosted(
workflow("[self-hosted, omni-release]", "npm publish --provenance-file=./p.json")
).length,
0,
"--provenance-file is a different flag (a pre-built bundle) and is not what the registry rejects"
);
assert.equal(
findProvenanceOnSelfHosted(workflow("self-hosted", "npm publish --provenance=true")).length,
1
);
});
test("does not flag hosted jobs, unknown runners, or self-hosted jobs without the flag", () => {
assert.deepEqual(
findProvenanceOnSelfHosted(workflow("ubuntu-latest", "npm publish --provenance")),
[]
);
assert.deepEqual(
findProvenanceOnSelfHosted(workflow("${{ matrix.os }}", "npm publish --provenance")),
[]
);
assert.deepEqual(
findProvenanceOnSelfHosted(workflow("self-hosted", "npm publish --access public")),
[]
);
// The word only in a step NAME or a comment is not a finding.
assert.deepEqual(
findProvenanceOnSelfHosted(
[
"name: t",
"on: push",
"jobs:",
" j:",
" runs-on: self-hosted",
" steps:",
" - name: provenance note",
" run: echo hi # --provenance later",
"",
].join("\n")
),
[],
"a comment after the command is still part of the run string — accept that the regex is conservative"
);
});
test("reusable-workflow jobs (uses:) and unparseable YAML are not this rule's findings", () => {
const reusable = [
"name: t",
"on: push",
"jobs:",
" j:",
" uses: ./.github/workflows/x.yml",
"",
].join("\n");
assert.deepEqual(findProvenanceOnSelfHosted(reusable), []);
assert.deepEqual(findProvenanceOnSelfHosted("jobs: [unclosed"), []);
});
test("regression guard: no workflow in this repo publishes with --provenance from a self-hosted runner", () => {
const files = readdirSync(WORKFLOWS).filter((f) => /\.ya?ml$/.test(f));
assert.ok(files.length > 10, "expected the real workflow set");
const findings = files.flatMap((f) =>
findProvenanceOnSelfHosted(readFileSync(join(WORKFLOWS, f), "utf8"), f)
);
assert.deepEqual(
findings,
[],
`npm rejects provenance from self-hosted runners (422) — move the upload to a github-hosted job: ${JSON.stringify(findings)}`
);
});