ci: add govulncheck reachability scanning
What
Adds reachability scanning to CI, which nothing in the pipeline does today.
chore(deps)— bringgoldmarkandx/imagecurrent.feat(ci)— a converter turninggovulncheck -format jsoninto a GitLab Dependency Scanning report.ci— agovulncheckjob: blocking on the default branch, manual on merge requests.
Why
The three security jobs we run answer a different question from the one govulncheck answers. SAST reads our own source. Both dependency scanners match versions against an advisory database, so they report anything present in the module graph regardless of whether we ever call it. None of them distinguishes a dependency we merely require from one whose affected code paths our binary actually reaches.
govulncheck does symbol-level call-graph analysis, so it reports only what glab genuinely calls. That is a much smaller and more actionable set than version matching produces.
The dependency updates
goldmark and x/image are indirect dependencies, which Renovate's gomod manager does not update — nothing in renovate/projects/cli.config.js or the shared config enables the indirect dep type. Both had drifted a long way as a result; x/image had been pinned at v0.20.0 since 2024-09-04.
Verified in a clean worktree: 2 lines in go.mod, 8 in go.sum, no dependency cascade, go build ./... clean, and the internal/utils and internal/commands/cluster/... tests pass.
The converter
Reports only symbol-reachable findings. govulncheck emits each advisory three times, at module, package and symbol level; the first two only say "this version is in our module graph", which the SBOM analyzer already covers. Filtering to symbol level is what keeps this from duplicating the existing scanners — in testing, five finding objects collapsed to one reported entry.
The ignore list follows gitaly's tools/govulncheck-filter: each entry requires an issue recording why the risk was accepted, so a green pipeline never quietly means someone added a line. It starts empty.
Output was validated against dependency-scanning-report-format.json v15.2.5 from security-report-schemas using ajv --spec=draft7 --strict=false: valid.
The job
Blocking on the default branch, when: manual with allow_failure: true on merge requests — the same shape as gitaly's vulnerability job.
The verdict depends on the advisory database as much as on the code, so a new upstream advisory can turn it red with no change on our side. Making that an author's problem would mean unrelated merge requests failing for something they did not do. On the default branch it belongs to whoever owns it. Authors who want the check can still start it from the pipeline view.
Notes for review
severityis alwaysUnknown. The Go vulnerability database carries no CVSS data, so there is nothing to map from. Consequence worth knowing: approval policies filter on severity, so they can never gate on these findings. The job's exit code is the gate instead.go build, notgo run.go runcollapses every non-zero status to 1, which would erase the difference between "found a reachable finding" (3) and "the scanner broke" (1). Verified: the built binary exits 3 / 0 / 1 for findings / clean / malformed input, whilego runreturns 1 for all three.govulncheckis invoked by path..go-cachepointsGOPATHat the project directory so the runner cache can pick up the module cache, which meansgo installwrites to$CI_PROJECT_DIR/.go/bin— not onPATHin thegolangimage.- Empty input is an error, not a clean report.
govulncheckalways emits aconfigmessage, so input without one means the scan did not run. Without this check, a crashed scanner would produce zero findings and a green pipeline. GOVULNCHECK_VERSIONis pinned so a scanner release cannot change the pipeline's verdict on its own. Renovate does not manage it; it needs bumping by hand.- The job sits in the
teststage. !3920 (merged) reorganises stages and will conflict here; whichever merges second needs a trivial rebase.