freenode
AnalysisLanguages & Toolchains

Go sumdb fixes reopen who anchors module trust

Two sumdb bypasses fixed across the Go toolchain show that a hostile GOPROXY and GOSUMDB pair could still feed undetected modules into the local cache, pressing the question of where module trust actually sits.

The Go module checksum database was supposed to make proxy mischief detectable. A cluster of coordinated security releases this week shows how incompletely that promise held when the proxy and the sum database were not independent honest parties. golang.org/x/mod v0.40.0, Go 1.26.6, Go 1.25.13, and go1.27rc3 all ship the same pair of fixes, CVE-2026-56865 and CVE-2026-56864, and the wording is blunt: under the old verification path, malicious module content could land in a developer’s local cache without the transparency log catching it.

The through-line across the golang-nuts, golang-dev, and oss-security announcements is not a routine patch dump. It is a restatement of the module supply-chain threat model. Go clients fetch modules through GOPROXY and check them against GOSUMDB, whose signed tree of module hashes is meant to be the public, append-only record that any observer can audit. That design assumes the client actually verifies the tree structure and only accepts hashes that are authenticated inside it. The two bugs were gaps in exactly those steps.

Filippo Valsorda of Geomys reported the tile path. As the Go Security team put it, "A malicious GOPROXY was previously capable of forging up to two sumdb tiles that allow for a requested module to bypass the GOSUMDB check and persist attacker-controlled module content to a local Go module cache." Tiles are the chunked Merkle tree fragments clients download to prove a leaf belongs under a signed tree head. If a proxy can hand over a couple of forged tiles that the client does not fully check against their parents, the GOSUMDB check can be made to pass for content that never appeared in the real log. "This attack allows for a malicious GOPROXY to serve malicious module content that cannot be detected by evaluating the transparency log." The fix is stated without ceremony: "All tiles are now correctly verified against their parents."

The second issue, reported by mundur, sits one layer up in Lookup. "A malicious GOSUMDB was capable of serving arbitrary module content not contained within the transparency log." The client was willing to accept unrelated, unauthenticated hashes returned alongside a lookup. With a coordinating proxy and sum database, that is enough to bless attacker-controlled module bytes. Again the team’s summary is direct: "This attack allows for a coordinating GOPROXY and GOSUMDB to serve a client malicious module content that cannot be detected by evaluating the transparency log." The remediation is to ignore those unauthenticated hashes.

Taken together, the CVEs sketch a coherent attack surface rather than two isolated mistakes. A lone malicious proxy could already cheat on tile verification. A pair that also controls the sum database endpoint could cheat on lookup results. Either way the failure mode is the same from the victim’s point of view: go get succeeds, the module cache retains bad content, and later inspection of the public transparency log does not surface the substitution. That is precisely the class of undetectable supply-chain injection the sumdb design was built to prevent.

The practical advice repeated in every announcement is operational, not architectural. To see whether a tree was already poisoned, wipe go.sum, go.work.sum, and vendor, then run go mod tidy so the client re-resolves and re-verifies. That recovers individual workspaces. It does not answer the harder deployment question the bugs surface.

Many organizations do not point GOPROXY and GOSUMDB at the public defaults. They run internal module proxies for availability, licensing, or air-gap reasons, and they often point GOSUMDB at a sum database they also operate, or they disable the sum check in constrained environments. The official releases treat "malicious GOPROXY" and "coordinating GOPROXY and GOSUMDB" as in-scope adversaries. That is a stronger threat model than "the public proxy is honest and only the network is hostile." It matches how enterprises actually wire the toolchain: the same administrative domain that can MITM or replace the proxy can often replace or misconfigure the sum database too. Under that pairing, client-side tree verification is the last line, not a belt-and-suspenders extra. When verification is incomplete, the line disappears.

Defenders of the existing design can steelman a clear position. The public sum.golang.org log remains a globally auditable anchor for anyone who still talks to it. The bugs were implementation defects in tile parent checks and in Lookup’s handling of extra hashes, not a collapse of the signed-tree idea. Once every client verifies tiles against parents and drops unauthenticated lookup material, a proxy that does not also control a trusted sum database endpoint cannot mint invisible modules. Companies that pin GOSUMDB to the public verifier, or that independently gossip and compare tree heads, still get the transparency property the design advertised. From that angle, shipping x/mod v0.40.0 and the point releases is the system working as designed: report, fix, force cache re-validation.

The opposing pressure is equally concrete. Valsorda’s and mundur’s findings show the client was the weak point. Transparency logs only detect what clients actually check. If forging "up to two sumdb tiles" was enough to bypass GOSUMDB, then years of ecosystem reliance on sumdb offered less assurance than the rhetoric claimed whenever a custom proxy was in path. Security reviewers who already distrust shared build infrastructure will read CVE-2026-56865 and CVE-2026-56864 as confirmation that GOPROXY is a trust root in practice, and that GOSUMDB is only as strong as the client’s tile and lookup code. The recommended tidy ritual also quietly admits persistence: bad content in the module cache survives until someone thinks to delete sums and vendor and fetch again.

Nothing in the releases re-litigates whether Go should have a single global sum database, whether proxies should be required to be transparent themselves, or how offline and enterprise mirrors ought to co-sign or cross-log. The patches close the known bypasses. They leave standing the operational reality that whoever runs the proxy a developer actually uses, and whoever answers GOSUMDB for that developer, jointly gate what enters the cache. Clients that never talk to a third-party auditor still cannot detect a coordinated lie by "evaluating the transparency log," because they never see the real log.

For now the ecosystem posture is update and re-tidy. Go 1.26.6, 1.25.13, the 1.27 release candidate, and x/mod v0.40.0 carry the corrected verification. The unresolved argument is the one the CVEs forced back into the open: when a shop runs its own GOPROXY and GOSUMDB, is the checksum database still an independent transparency layer, or is it just another config knob on the same trust root? The fixes make the honest path verifiable again. They do not decide who must be honest.