Dependency Scanning with govulncheck & SBOM
Go modules plus govulncheck give you a first-class supply-chain signal in CI, while SBOM export answers auditors and security teams asking what ships in each binary.
Treat vulnerability scanning as a gate, not a one-time checkbox.
Summary
Your service imports dozens of transitive modules.
The Go vulnerability database records CVEs affecting standard library and third-party packages.
govulncheck analyzes which imported symbols are actually reachable from your code and reports relevant findings.
SBOMs (Software Bill of Materials) list components and versions for compliance, license review, and faster incident triage when a new CVE lands.
Pair scanning with go mod tidy, pinned CI Go versions, and a patch SLA.
Recipe
Quick-reference recipe card - copy-paste ready.
go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./...When to reach for this:
- Every CI pipeline on merge and release tags.
- Before promoting containers to production registries.
- After Dependabot or manual module bumps to confirm findings cleared.
- Compliance programs requiring SBOM attachment per artifact.
Working Example
#!/usr/bin/env bash
set -euo pipefail
# CI job excerpt for example.com/myapi
export GOTOOLCHAIN=local
go version
go mod download
go mod verify
echo "== govulncheck =="
govulncheck -json ./... > govulncheck.json
# Fail on any finding in CI (tune with allowlist file if needed)
if jq -e '.finding | length > 0' govulncheck.json >/dev/null 2>&1; then
echo "vulnerabilities found"
jq '.finding' govulncheck.json
exit 1
fi
echo "== SBOM (CycloneDX) =="
go install github.com/CycloneDX/cyclonedx-gomod/cmd/cyclonedx-gomod@latest
cyclonedx-gomod mod -json -output sbom.json// main.go - minimal module for scanning demos
package main
import (
"encoding/json"
"net/http"
)
func main() {
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
})
http.ListenAndServe(":8080", nil)
}What this demonstrates:
go mod verifyconfirmsgo.sumentries match downloaded modules.govulncheck -jsonoutput suitable for CI parsing and dashboards.- Failing the build when findings exist (policy choice).
- CycloneDX SBOM generation from the module graph for artifact storage.
Deep Dive
How It Works
go modresolvesrequiredirectives into a module graph recorded ingo.sum.- Proxy downloads (
GOPROXY) serve zip hashes; tampering breaks verification. govulncheckperforms static analysis to see if vulnerable functions are called from your packages, reducing noise versus naive CVE scanners.- SBOM tools (
cyclonedx-gomod,syft) emit JSON listing modules, versions, and hashes for attachment to container images or release pages. - Container builds should run scans on the same
go.modcommit baked into the image tag.
CI Pipeline Stages
| Stage | Command | Purpose |
|---|---|---|
| Verify | go mod verify | Detect sum tampering |
| Tidy check | go mod tidy diff fail | Prevent drift |
| Vuln scan | govulncheck ./... | CVE signal |
| SBOM | cyclonedx-gomod mod | Compliance artifact |
| Build | go build -trimpath | Reproducible binary |
Handling Findings
| Severity | Action |
|---|---|
| Reachable stdlib CVE | Bump Go patch release in CI image |
| Reachable module CVE | Upgrade module; run tests |
| Unreachable finding | Document false positive; re-check on upgrades |
| No fix yet | Track exception with expiry; mitigate if possible |
Go Notes
# Pin toolchain in go.mod for reproducible CI
# go.mod line: go 1.26
# Private modules
export GOPRIVATE=github.com/myorg/*
export GONOSUMDB=github.com/myorg/*Gotchas
- Skipping govulncheck because go.sum exists - Checksums prove integrity, not absence of known CVEs.
- Only scanning direct requires - Transitive modules introduce most findings; scan
./...at package level. - Ignoring stdlib vulnerabilities - Go release notes include security fixes; old patch versions stay vulnerable.
- SBOM generated once manually - Drift from CI makes audit artifacts stale. Generate per build from locked
go.sum. - replace directives to unvetted forks -
replacebypasses normal provenance; review like first-party code. - GONOSUMDB too broad - Disabling sum verification for whole domains removes tamper detection.
- Green scan without upgrading -
govulncheckmay report no reachable vulns on an old version until symbols change; still bump per release policy.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| GitHub Dependabot / Renovate | Automated bump PRs | You lack CI tests to validate bumps |
| Container scanners (Trivy, Grype) | Image OS packages plus binary layers | Substitute for Go-specific reachability analysis |
| Snyk / commercial SCA | Enterprise policy dashboards | Budget or air-gapped builds block SaaS |
go list -m all manual review | Tiny modules | Large graphs need automation |
FAQs
How often should govulncheck run?
On every pull request and release build at minimum.
Nightly scheduled runs catch new CVEs published after your last merge.
Does govulncheck modify go.mod?
No.
It reports findings; you bump versions with go get -u or targeted go get module@patch.
What is the difference between reachable and unreachable findings?
Reachable means your code imports and calls a vulnerable symbol.
Unreachable findings may still warrant upgrades but are lower priority.
Should SBOM include the standard library?
Yes for complete transparency.
CycloneDX gomod includes module dependencies; document Go toolchain version separately in release metadata.
How do private modules affect scanning?
govulncheck analyzes your code graph; private module code is included.
Vulnerability data comes from the public Go VulnDB for public modules and stdlib.
Can I allowlist a finding temporarily?
Document risk acceptance with owner, reason, and expiry date.
Re-scan weekly; remove allowlist when fix is released.
Does vendoring change scanning?
Run govulncheck against the same packages; vendored code is what compiles.
Keep vendor/ synced with go mod vendor in CI.
What Go version should CI use?
Match go directive in go.mod and patch level your security policy requires.
Use GOTOOLCHAIN or container image pins for consistency.
How do SBOMs help during incidents?
When a new CVE drops, search SBOMs for affected component versions across services without re-scanning live clusters blindly.
Is license compliance part of SBOM?
SBOM lists components; license classification may need separate tools.
Still valuable for legal review of transitive deps.
Should I commit go.sum?
Yes.
It enables reproducible builds and go mod verify for all collaborators and CI.
How does this relate to OWASP supply chain guidance?
SBOM plus vuln scanning addresses known vulnerable components (CWE-1104).
Pair with signed commits, protected branches, and minimal dependency policy.
Related
- Go Security Basics - local govulncheck command
- Security for Go Services: Defaults and Gaps - supply chain in the security model
- Go Toolchain Basics - module and build commands
- CI Quality Gates: Lint, Test, Race & Vuln - CI quality gates alongside security
- Security Best Practices Summary - dependency scanning checklist items
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify at build).