From f83a272f8a7ffc07df64babc8e18a3c8607fade0 Mon Sep 17 00:00:00 2001 From: Warwick Date: Thu, 6 Aug 2026 08:37:56 -0500 Subject: [PATCH] ci: gate PRs on govulncheck We generate SBOMs on every PR but never fail on a vulnerability. sbom.yml runs trivy twice purely for inventory (spdx-json and cyclonedx), and pr.yaml covers tests, lint, build and e2e. Nothing blocks a PR that introduces a known-vulnerable dependency. The existing security tooling covers other axes: CodeQL (via GitHub default setup), Mend SAST, and GitGuardian are all SAST or secrets. Dependency vulnerabilities were the uncovered one, which is how klauspost/compress and go.opentelemetry.io/otel sat at versions with published advisories until they were found by hand. Adds a govulncheck job. It was chosen over a trivy gate specifically for its failure mode: govulncheck does reachability analysis and fails only when our code actually calls a vulnerable symbol. Advisories in modules we merely require are reported without failing. That distinction matters here. We carry three advisories that cannot be fixed by any version bump: GO-2026-5932 golang.org/x/crypto openpgp, unmaintained GO-2022-0646 aws-sdk-go v1 s3crypto CBC padding oracle GO-2022-0635 aws-sdk-go v1 s3crypto in-band key negotiation All are Fixed in: N/A, and aws-sdk-go v1 stays in the graph regardless of our own code because opencost's cloud provider package imports it. A scanner that failed on unreachable findings would be red from day one and would need a permanently maintained ignore list. Verified against both develop and the current dependency branch: govulncheck exits 0 on each, so this gate is green on arrival and has no ordering dependency on the in-flight dependency PR. The job runs independently of build-and-test rather than as a step inside it, so a failing unit test cannot mask a vulnerability or vice versa. It inherits this workflow's nightly cron, which is where most of the value sits: newly published advisories get caught against unchanged code instead of waiting for someone to open a PR. govulncheck is pinned to v1.6.0 rather than @latest so the scanner cannot change under a review in progress. This does not stale the data, as the advisory database is fetched from vuln.go.dev at run time. set-labels is deliberately left untouched. Its needs list drives a label that reads "unit tests passed/failed", and wiring a vulnerability result into it would make that label mean something it does not say. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/pr.yaml | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 39e144d..006d1d1 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -15,6 +15,42 @@ on: types: [checks_requested] jobs: + # Fails the build only when our own code actually calls a vulnerable symbol. + # govulncheck performs reachability analysis, so advisories in modules we + # require but never call are reported without failing the job. That matters + # here: we currently carry three such advisories that have no fix available + # (golang.org/x/crypto openpgp, and two aws-sdk-go v1 s3crypto issues), and a + # scanner that failed on those would need a permanently-maintained ignore + # list. This job needs none. + # + # Runs independently of build-and-test so a failing unit test does not mask a + # vulnerability, and vice versa. It also inherits this workflow's nightly + # cron, which is where most of the value is: newly published advisories are + # caught against unchanged code, without waiting for someone to open a PR. + vuln-scan: + name: Vulnerability scan + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - uses: actions/checkout@v6 + + - name: Install Go + uses: actions/setup-go@v5 + with: + go-version: 'stable' + + # Pinned rather than @latest so the scanner itself is reproducible and + # cannot change under us mid-review. This does not stale the advisory + # data: govulncheck fetches the vulnerability database from vuln.go.dev + # at run time, so a pinned binary still sees newly published advisories. + - name: Install govulncheck + run: go install golang.org/x/vuln/cmd/govulncheck@v1.6.0 + + - name: Run govulncheck + run: govulncheck ./... + build-and-test: runs-on: ubuntu-latest permissions: