Repository navigation
Description
Split out of #529, which closed 2026-08-30 having ported repo-template's fail-open --level fix. That issue's body named this explicitly and it was never filed:
Adopting the
PARSERSregistry is a legitimate follow-up, but it is a refactor and belongs in its own PR — not bundled with a security fix.
Filing it now rather than leaving it to rot in a closed issue, per the repo's own rule. Nothing is broken — this is a maintainability change, not a defect.
The problem
.github/scripts/vuln-gate.mjs (378 lines) hand-dispatches on ecosystem in four places, each of which must be edited together to add a third ecosystem:
| Site | Line | Shape |
|---|---|---|
ECOSYSTEMS set |
35 | new Set(["npm", "nuget", "any"]) |
reportProblem |
294 | if (ecosystem === "npm") … else if (ecosystem === "nuget") … |
main() arg check |
~343 | options.ecosystem !== "npm" && options.ecosystem !== "nuget" |
| parser selection | ~353 | options.ecosystem === "npm" ? parseNpm(parsed) : parseNuget(parsed) |
The last one is the sharp edge: it is a ternary with no third branch, so a new ecosystem that clears the ECOSYSTEMS guard but is not wired into the ternary is silently parsed as nuget. Wrong findings, green gate.
This is the hand-maintained-list shape AGENTS.md already warns about under Writing a guard — "prefer walk everything, exclude deliberately over list what I thought of". A PARSERS registry keyed by ecosystem makes the four sites one, and makes the set of supported ecosystems derivable rather than restated.
Scope
- Replace the four sites with a single registry mapping ecosystem →
{ parse, validateShape }. - Derive
ECOSYSTEMS, the usage string, and the arg check from the registry's keys rather than restating them."any"stays valid for exception scope while not being a parseable ecosystem — that asymmetry is real and must survive the refactor. - Keep every exported function exported.
parseNpm,parseNuget,gate,severityRank,levelProblem,isValidException,emitAllowlistand the rest are the tested API surface; this is an internal dispatch change, not an API change.
Guard
Per AGENTS.md, the guard comes with a mutation that makes it red before any claim is written: add a registry entry with no parser and assert the gate refuses to run rather than falling through to a default parser. A test that only checks "npm and nuget still work" would pass with the bug present and is not sufficient.
Explicitly out of scope
The gate()-level --level validation residual from #529 — an unvalidated level reaching the exported gate() gives floor = -1, so every finding blocks. That is fail-closed, not fail-open, and #529 filed it against collectify#117 with "do not repeat it here". Leave it alone.
Not epic-tracked
#529 carried no epic label and neither does this — it is CI tooling, not product work, so there is no phase-epic checklist to add it to.
Activity
chore(ci): replace vuln-gate's four hand-dispatched ecosystem sites w…