Safeguard
Supply Chain Security

Anatomy of a Malicious Go Package Typosquat

A Go typosquat backdoored since 2021 stayed live for over three years because Go's module proxy caches code immutably — even after the attacker cleaned the repo.

Safeguard Research Team
Research
6 min read

On January 30, 2025, researchers at Socket reported a typosquat of github.com/boltdb/bolt — a key-value store with 8,367 dependent packages — to GitHub and Google. The malicious package, published as github.com/boltdb-go/bolt, had shipped a command-and-control-connected remote-access backdoor in its v1.3.1 release since November 2021. The backdoor itself used only light string-manipulation obfuscation to hide its C2 address — the attacker's real leverage wasn't clever malware engineering, it was exploiting a structural property of Go's module ecosystem. After the Go Module Proxy (proxy.golang.org) cached the backdoored v1.3.1, the attacker quietly rewrote the GitHub repository's Git tags to point at clean, benign code. Anyone inspecting the source on GitHub saw nothing wrong. But because GOPROXY serves cached module content immutably — a deliberate reliability guarantee so builds stay reproducible even if upstream disappears or rewrites history — go get kept resolving to the backdoored version regardless. The result: a live backdoor that survived undetected for over three years, unaffected by the attacker's own cleanup. This is a fundamentally different failure mode than npm or PyPI typosquats, where a compromised version can at least be pulled from the registry. This post breaks down how the attack worked and how to vet Go dependencies before they end up in your module graph.

Why does Go's module proxy make typosquats harder to kill than npm or PyPI equivalents?

Because proxy.golang.org caches modules immutably by design, and that immutability is a feature, not a bug — it's what makes Go builds reproducible even years later. Once any developer anywhere runs go get against a module version, the proxy fetches and permanently caches that exact content, keyed to its cryptographic hash in the Go checksum database (sum.golang.org). npm and PyPI both allow a maintainer or registry operator to unpublish or yank a malicious release, cutting off new installs immediately. Go's proxy has no equivalent "delete" path for cached content by default. In the boltdb-go/bolt case, this meant the attacker's post-hoc repository cleanup — rewriting tags to point at innocent code — did nothing to stop new installs of the cached malicious v1.3.1. Socket's research frames this plainly: the same design that protects legitimate Go projects from left-pad-style disappearing dependencies also protects an attacker's malicious release from ever being retracted.

How did the boltdb-go/bolt attack actually persist for three years?

The attacker ran a three-step play documented by Socket: publish a typosquat under a name close to a trusted package, get the malicious version cached by the module proxy, then erase the evidence at the source. boltdb-go/bolt differs from the real boltdb/bolt only in an inserted hyphenated segment — an easy misread or copy-paste error in a go.mod require line or an AI-assisted code suggestion. Once v1.3.1 was published and any single go get cached it in November 2021, the attacker rewrote the GitHub repo's tags to reference clean code, so a manual audit of the visible repository would find nothing suspicious. The backdoor itself established outbound connectivity consistent with remote access and control, according to Socket's writeup, which was independently covered by The Hacker News and Germany's heise.de with consistent details. The gap between compromise (November 2021) and public disclosure (reported to GitHub/Google January 30, 2025, published February 4, 2025) is the headline number: over three years of exposure for anyone who had ever resolved that module path.

Does this happen more than once, or was BoltDB an isolated case?

Socket's research into Go typosquatting documented this as part of a broader pattern of "trust-then-poison" behavior in the Go ecosystem, where a package mirrors a legitimate project's releases faithfully for an extended period before shipping malicious code in a later version — the opposite of an obviously suspicious brand-new package. The core structural risk is ecosystem-wide, not limited to one incident: any Go module path close enough to a popular import to survive a quick visual check is a candidate, and because go.mod files pin exact module paths rather than resolving by a package registry search (unlike npm install <name>), a single typo in a require line, a bad AI-suggested import, or a copy-pasted go get command from an untrusted forum post is enough to pull in the wrong module permanently once it's cached locally or in a shared module cache/CI runner.

What should teams actually check before adding a new Go dependency?

Verify the module path character-by-character against the canonical upstream (check pkg.go.dev's listed repository link, not just search results), confirm the GitHub organization and star/commit history match what you expect for a package with the claimed adoption, and check go.sum diffs in every PR that changes go.mod — a new indirect dependency with an unfamiliar path is worth a manual look. Run go mod why <module> to confirm why a dependency landed in your graph at all, since typosquats frequently arrive as unreviewed transitive dependencies rather than direct imports. Pin exact versions and review go.sum hash changes on every update; a hash change on a version number you've already used before is a red flag GOPROXY's immutability should make essentially impossible for legitimate re-publishes. Finally, treat Go modules with the same registry-vetting discipline teams already apply to npm and PyPI — most Go tooling historically hasn't had the equivalent of npm audit for malware, only for known CVEs, which is a narrower net.

How Safeguard helps

Eagle, Safeguard's malware-classification model, scores every Go module publish, checking specifically for typosquat similarity — module paths within edit-distance 2 of a top-1000 package published under a different, unassociated account — as well as typosquat rotation, where an attacker republishes near-identical names after one gets flagged. Because Eagle re-scores its full historical corpus whenever it's updated with a new detection signal, a Go module already sitting in your go.sum can surface as a finding retroactively, the same mechanism that would have caught a boltdb-go/bolt-style backdoor without waiting on an external disclosure. For teams that want to remove the vetting burden from individual engineers, Safeguard's Gold registry offers hardened, continuously revalidated forks of widely-used Go modules — zero known critical or high CVEs and cleared by Eagle — as a drop-in substitute for the packages most worth typosquatting in the first place.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.