Day 159 — govulncheck & gosec¶
Month 6 · Week 3 · ⬅ Day 158 · Day 160 ➡ · Journal index
🎯 Learning Objective¶
Scan a Go project two complementary ways — govulncheck for reachable known vulnerabilities in dependencies/stdlib, and gosec for risky code patterns — and wire both into CI.
📚 Topics¶
govulncheck: reachability-based CVE scanning from the Go vuln databasegosec: AST-based linter for insecure code (secrets, weak crypto, SQLi)- Where each fits in CI; triaging and suppressing findings honestly
📖 Reading / Sources¶
-
govulncheckdocs · Go blog — govulncheck - Go vulnerability database (pkg.go.dev/vuln)
- gosec — Go security checker
- Go security best practices
📝 Notes¶
govulncheckis reachability-based, which is its superpower: it builds your call graph and only reports vulnerabilities your code actually calls, not every CVE that merely exists ingo.sum. Far less noise than naive SCA scanners → [[govulncheck]].- It scans both third-party modules and the standard library (a vulnerable Go toolchain version is flagged too). Source data is the curated Go vuln DB.
- Run modes:
govulncheck ./...(source mode, needs the code + reachability) orgovulncheck -mode=binary ./app(scan a built binary — great for verifying released artifacts).-format=jsonfor CI parsing. - A finding has two tiers: "Vulnerability + your code calls it" (act now) vs "in your module graph but not called" (informational). Prioritize the called ones.
gosecis pattern/AST-based: it flags insecure code, e.g. hardcoded credentials (G101),math/randfor security (G404), SQL string concatenation (G201/G202), weak TLS/crypto (G402/G401), command injection (G204), unhandled errors (G104), file perms (G302/G306). It complementsgovulncheck— different layer (your code vs known CVEs) → [[gosec]].- Triage, don't blanket-suppress. For a true false positive, annotate the exact line
// #nosec G404 -- non-crypto jitter, reviewedwith a reason, or exclude a rule deliberately (-exclude=G104). Silent global suppression hides real bugs. - CI placement: run
govulncheckon a schedule and per-PR (new CVEs appear without code changes). Gate the build on reachable HIGH/CRITICAL; treat unreachable ones as warnings.gosecruns per-PR on changed code.go vetandstaticcheckround out correctness/lint but aren't security scanners. - Keep the toolchain itself updated (
goversion, base images by digest from [[day-150]]) — many stdlib CVEs are fixed simply by bumping Go. - These are CLI tools, not library code in your service, so there's no stdlib example — the snippets below are the real invocations.
💻 Code Examples¶
# Install (module-versioned tools).
go install golang.org/x/vuln/cmd/govulncheck@latest
go install github.com/securego/gosec/v2/cmd/gosec@latest
# Reachable-vulnerability scan of the whole module (source mode).
govulncheck ./...
# Scan a released binary instead of source.
govulncheck -mode=binary ./bin/app
# Machine-readable for CI gating.
govulncheck -format=json ./... > vulns.json
# Security linter over the code; fail CI on any finding.
gosec ./...
gosec -severity=medium -confidence=medium -fmt=sarif -out=gosec.sarif ./...
# GitHub Actions: block PRs on reachable vulns and insecure patterns.
- name: govulncheck
run: |
go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./...
- name: gosec
uses: securego/gosec@master
with:
args: -severity medium ./...
🏋️ Exercises / Practice¶
| Exercise | Status | Link |
|---|---|---|
| (Reinforces Day 156/157 allocation-safe code patterns) | ✅ | exercises/month-06/week-3/ |
No stdlib runnable example today —
govulncheckandgosecare external CLIs run against the repo, shown as the commands above.
🐛 Mistakes Made¶
- Panicked at a long
go list -m allCVE list from a naive scanner;govulncheckshowed only the 2 my code actually reaches. Reachability cut the noise massively. - Used
// #nosecwith no rule id or reason on a real finding. Replaced with a specific// #nosec G404 -- reasonafter confirming it was non-security randomness.
❓ Open Questions¶
- How to keep
govulncheckgreen continuously without alert fatigue — schedule cadence vs per-PR, and auto-PRs for safe bumps (Dependabot/renovate)?
🧠 Active Recall (answer without looking)¶
- Q: Why does
govulncheckreport fewer issues than a typical SCA tool that readsgo.sum?A
It's reachability-based: it analyzes the call graph and reports a vulnerability only if your code actually calls the affected symbol. Dependencies that contain a CVE but whose vulnerable function you never invoke are downgraded to informational, eliminating most false-positive noise.
2. Q: govulncheck vs gosec — what different layers do they cover? A
govulncheck finds known vulnerabilities (CVEs from the Go vuln DB) in your dependencies and the standard library that your code reaches. gosec is a static linter that finds insecure code patterns you wrote (hardcoded secrets, weak crypto, SQL concatenation, math/rand for tokens). One scans the supply chain; the other scans your own source.
🪶 Feynman Reflection¶
Two different security guards. govulncheck reads the "wanted posters" (the vuln database) and checks whether anyone you actually talk to (your reachable call graph) is on them — ignoring strangers you never contact. gosec is the inspector walking through your own house looking for unlocked doors you left: passwords on sticky notes, a flimsy lock (weak crypto), a window you can climb through (SQL injection).
🕳️ Knowledge Gaps¶
- SARIF integration with GitHub code scanning and dedup across runs.
- Building an SBOM and tying it to vulnerability monitoring over time.
✅ Summary¶
I can scan a Go project for reachable known vulns with govulncheck (source and binary modes) and insecure code patterns with gosec, triage findings honestly with scoped suppressions, and gate CI on the ones that matter.
⏭️ Next Steps / Prep for Tomorrow¶
- Day 160: harden the running service — authentication/authorization and rate limiting.
| Time spent | Difficulty | Confidence |
|---|---|---|
| 90 min | 🟦🟦⬜⬜⬜ | 🟦🟦🟦⬜⬜ |
Suggested commit: docs(journal): govulncheck & gosec scanning (day 159)