The Go checksum database is one of the quiet achievements of the Go module system. It is a cryptographically verifiable transparency log of every publicly released module version, maintained at sum.golang.org since August 2019. It is the reason Go has had fewer publicized typosquatting and dependency-confusion incidents than npm or PyPI. But the system only works if you understand the verification patterns, use them correctly, and do not bypass them under pressure. Here is what I think about when designing Go build environments.
What does go.sum actually contain?
Every line in go.sum is one of two forms:
example.com/foo v1.2.3 h1:abcdef...
example.com/foo v1.2.3/go.mod h1:qrstuv...
The first is the hash of the module's zip contents. The second is the hash of the module's go.mod file on its own. Both hashes are base64-encoded SHA256 prefixed with h1:, which is the hash algorithm identifier. The go.mod-only hash exists because during minimum version selection, the Go command may need to inspect a module's go.mod without downloading the full module.
When go build runs, every module needed for the build must have a matching entry in go.sum. If a hash differs from the computed hash of the downloaded content, the build fails with checksum mismatch. This is the local verification layer.
The sumdb layer
The remote verification layer is sum.golang.org, the public Go checksum database. It is a Merkle-tree transparency log, implemented using the same principles as Certificate Transparency. Every module version ever fetched through the public proxy is recorded as a leaf in the tree, and the tree head is signed and published.
When the Go command encounters a module for the first time and does not have an entry in go.sum, it asks sum.golang.org for the official hash. It also asks for a tree proof that the hash is included in the signed log at the current head. If an attacker ran a compromised proxy that served a modified module, the Go command would compute a different hash than what the sumdb returned, and the build would fail.
The sumdb itself is cryptographically verified by witnesses. Clients can cross-check the tree head they receive against independently-operated witness servers to detect if the sumdb operator were to serve a split view.
GOSUMDB settings
GOSUMDB=sum.golang.org is the default. You can disable it with GOSUMDB=off, which is dangerous on public modules because it turns off the remote verification layer. You can point it at a private sumdb for internal module verification, which is what Google does internally and what some large Go shops do as well.
The Go command caches sumdb responses locally, so you do not make a network request for every module on every build. The cache lives under $GOPATH/pkg/mod/cache/download/sumdb. If you want fully offline builds, pre-populate this cache on a networked machine and copy it.
What happens when go.sum disagrees with sumdb?
This is important and sometimes misunderstood. If go.sum already has an entry and the downloaded module's hash matches, the build succeeds. The sumdb is not consulted. This means that once a hash is written into go.sum, it is trusted, and a later sumdb compromise or content change does not affect the build.
If go.sum does not have an entry, the Go command fetches the module, computes the hash, consults the sumdb for the authoritative hash, and writes the entry. If the computed hash does not match the sumdb hash, the build fails and nothing is written.
This means the critical moment is the first time a module version is added to a project. That is when the sumdb verification happens. Every subsequent build trusts go.sum. So reviewing go.sum diffs in PRs is the place to catch hash mismatches that might indicate a proxy compromise.
Vendoring and checksum verification
If you vendor dependencies with go mod vendor, the Go command writes a vendor/modules.txt that pins exact versions, and the vendored source is used directly without consulting the proxy or computing hashes at build time. The integrity guarantee shifts from the sumdb to your code review process: any change to vendor/ is visible in the git diff and must be reviewed.
Vendoring is a strong pattern for production builds because it makes the dependency graph auditable in the repository. It does add repository bloat, but the tradeoff is usually worth it for security-sensitive projects.
Go 1.22 added workspace vendoring with go work vendor, so multi-module projects can vendor too. Before 1.22, you had to choose between workspaces and vendoring.
Can you yank a bad version?
The Go module system supports deprecation but not true yanking. Once a version is published and added to the sumdb, it stays there forever. You can mark a module as deprecated or a version as retracted, which the Go command will respect when selecting versions, but the hash is still in the log.
This is by design. Transparency logs are append-only for a reason. But it means that if a compromised module version makes it into the sumdb, you cannot remove it. You can only retract it and publish a new fixed version. Anyone who still has that version in their go.sum will continue to build with it unless they explicitly update.
How should CI verify checksums?
My default CI pattern for Go builds:
- Set
GOFLAGS=-mod=readonlyso the build fails if it would need to modifygo.modorgo.sum. - Run
go mod download -xas a separate step so checksum fetches are visible in logs. - Run
go build ./...with no network-modifying flags. - After the build, run
go mod verify, which checks that every module in the local cache has the hash recorded ingo.sum.
This sequence makes every integrity check explicit and gives you log entries you can audit.
A real incident to remember
In October 2022, the rsc.io/quote module had a test case that turned into a real-world example of how sumdb catches tampering. A researcher set up a proxy that served modified content and demonstrated that the Go command refused the build. Similar exercises have been documented against private proxy setups. The pattern is always the same: proxy returns bytes, Go command computes hash, Go command compares to sumdb, mismatch detected, build fails.
There is no equivalent in most other language ecosystems at this level of rigor. npm has added provenance attestations more recently. PyPI has been rolling out sigstore integration. RubyGems has added SHA256 checksums. But Go has had end-to-end transparency-log verification built in for years.
Private module checksum patterns
For private modules, GOPRIVATE disables sumdb lookups because the public sumdb does not know about private modules. Verification relies on go.sum alone, which is verified against the content returned by your VCS or private proxy. This is weaker than the public path because there is no external witness.
If you want stronger private module verification, run your own sumdb. Google's sumdb code is open source and several companies run internal instances. It adds operational overhead but gives private modules the same transparency guarantees as public ones.
How Safeguard Helps
Safeguard monitors go.sum across every repository and correlates entries with the public checksum database to detect mismatches, yanked versions, and sudden hash changes. When a PR modifies go.sum, Safeguard annotates the diff with provenance and vulnerability context for every changed hash. Policy gates require that all builds verify against sum.golang.org or an approved private sumdb, and Safeguard records the go mod verify result as part of the build attestation so that integrity evidence travels with the artifact.