Pin Go dependencies by exact version; never `@latest` in CI
byob-security.1
security
Problem: go get example.com/pkg@latest, go install tool@main, or a
Dockerfile that installs a tool without a version all produce
non-reproducible builds. Worse, if an attacker compromises a
package's main branch or publishes a new tag, the next CI run picks
up the malicious version silently.
Idea: go.mod already pins to exact semver, and go.sum records the
cryptographic hash of every downloaded module. The discipline is to
never undermine that:
- No
@latest,@main, or branch names ingo get/go installanywhere that runs repeatedly (CI, Dockerfiles, Makefile targets). Always an explicit version:go install golang.org/x/vuln/cmd/govulncheck@v1.1.3. go mod tidyruns in CI and fails the build if it produces a non-empty diff — catches hand-editedgo.modthat skippedtidy.- Upgrades go through PRs (Dependabot or hand), never ad-hoc on a developer laptop.
Tradeoffs: pinned versions need periodic refreshing. That's what Dependabot / Renovate are for. The alternative — floating refs — is trading a small maintenance cost for a large supply-chain exposure.
When not to use: never. Pinning is the default stance for any build the project will do more than once.
Design
# CI step that fails on un-tidied go.mod:
go mod tidy
git diff --exit-code go.mod go.sum
# tool installation (note the explicit version):
go install golang.org/x/vuln/cmd/govulncheck@v1.1.3
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: gomod
directory: /
schedule: { interval: weekly }
- package-ecosystem: github-actions
directory: /
schedule: { interval: weekly }