In November 2021, someone published github.com/boltdb-go/bolt v1.3.1 to GitHub — a near-perfect typosquat of boltdb/bolt, the embedded key-value store used by organizations including Shopify and Heroku. Go's module proxy, proxy.golang.org, fetched that version and cached it permanently, exactly as designed: Go's module system guarantees that once a version is resolved, it stays byte-for-byte reproducible forever. The attacker then rewrote the Git tags on the source repository to point at a clean, benign commit, so anyone inspecting the GitHub page saw nothing wrong. The malicious build kept shipping from the proxy cache regardless, backdooring every go get that resolved it with remote C2 access. Socket's research team reported the package to GitHub and Google in early February 2025 — meaning the backdoor sat live for roughly three years before anyone caught it. A related case, shopsprint/decimal typosquatting the popular shopspring/decimal library, was weaponized in August 2023 using a DNS TXT-record command channel. Neither case required a novel exploit — both relied on developers mistyping an import path and a proxy architecture that makes takedowns nearly meaningless once a version is cached. This piece breaks down why that combination is so durable, and what actually stops it.
What made the boltdb-go/bolt typosquat effective?
It worked because it exploited two things at once: human typing error and package-name confusion. boltdb-go/bolt reads as a plausible GitHub organization variant of the real boltdb/bolt, the kind of naming difference a developer skims past when copy-pasting an import path from a Stack Overflow answer or an internal wiki. Once a single go.mod anywhere in an org pinned the malicious path, go mod tidy and CI pipelines would keep resolving it automatically on every subsequent build, with no interactive prompt to catch the mistake. The payload gave attackers a persistent command-and-control channel into any service that imported it — a meaningful blast radius given that boltdb/bolt is a foundational dependency for embedded storage in Go services, not a leaf utility most teams would notice missing.
Why did rewriting the Git tags not fix anything?
Rewriting the Git tags didn't fix anything because Go's module proxy doesn't re-fetch a version it has already cached — that immutability is the entire point of the design. proxy.golang.org exists to guarantee that go build produces the same output today as it did a year ago, so once it downloads and hashes a specific module version, it serves that exact cached artifact indefinitely, regardless of what the upstream Git repository now contains. When the boltdb-go/bolt attacker moved the v1.3.1 tag to point at an innocent commit, anyone browsing GitHub saw clean code, but any build tool still pulling from the proxy — the default behavior for GOPROXY since Go 1.13 — kept receiving the original malicious bytes. This is the mechanism Socket researchers highlighted as the core problem: the same reproducibility guarantee that protects developers from silent upstream tampering also protects an attacker's malicious version from ever being un-published once it's cached.
Does deleting or reporting the malicious repo remove it from builds?
No — deleting the GitHub repository or reporting the account does not remove a cached version from the Go module proxy or from Go's separate checksum database, sum.golang.org. The proxy and sumdb operate independently of the source host; they persist a permanent record of every module version's hash the first time it's requested. That's a deliberate anti-tampering feature — it stops an attacker from silently swapping a legitimate release after the fact — but it also means a malicious release, once resolved by even one build anywhere, becomes essentially permanent public infrastructure until Google's module mirror team manually intervenes to block it. This is a structurally different problem from npm or PyPI, where a maintainer or registry admin can usually pull a compromised version outright within hours of a report.
How does go.sum verification actually stop this class of attack?
go.sum verification stops a substituted or tampered module from silently making it into your build, but it does not stop you from trusting a malicious package name in the first place. Every entry in go.sum records a cryptographic hash of a specific module version; go mod verify recomputes those hashes against your local module cache and fails the build if anything has changed since the dependency was first added. Combined with GOSUMDB left at its default (sum.golang.org, rather than turned off) and GOFLAGS=-mod=readonly in CI, this closes the door on a compromised proxy response or a man-in-the-middle swap. What it can't do is warn you that boltdb-go/bolt isn't the package you meant to import — that's a typosquat problem, not an integrity problem, and it requires a human or a scanner reviewing the actual import path before it ever reaches go.sum.
What should teams actually change in their Go dependency workflow?
Teams should pin GOPROXY to a proxy they control or trust — proxy.golang.org directly, or a private proxy such as Athens or JFrog Artifactory — rather than leaving it resolvable to arbitrary source hosts via GOPROXY=direct, and should commit go.sum and run go mod verify as a CI gate rather than an optional step. Reviewing new dependency additions in pull requests matters more in Go than in some ecosystems precisely because a single mistyped import string, once merged, propagates through every subsequent build automatically and is difficult to retroactively purge from the proxy layer. Treat any new module addition as worth a second look at the exact import path against the real upstream, not just a glance at the package name.
How Safeguard helps
Safeguard's software composition analysis flags Go modules with anomalous naming patterns, low or inconsistent maintainer activity, and hash mismatches against known-good sumdb records, catching typosquat attempts before they land in a go.mod that CI will keep resolving forever. For teams that want to remove the guesswork around trusting arbitrary upstream Go modules entirely, Safeguard's Gold registry includes hardened Go artifacts — curated, continuously rescanned builds shipped with a CycloneDX SBOM, a Sigstore signature, and SLSA build provenance — so a pinned dependency comes with verifiable evidence of what it actually is, not just a name that resembles something legitimate. Neither replaces disciplined go.sum hygiene, but together they shrink the window between "someone typos an import" and "a security engineer notices."