North Korea-Linked Graphalgo Malware Published Through Go Modules and Terraform Providers
Published on gastropod.io on 09-29-2026
North Korea-Linked Graphalgo Malware Published Through Go Modules and Terraform Providers
Tags: vulnerability-intelligence, software-supply-chain-security, go, terraform, malware, typosquatting, north-korea
Researchers have found Graphalgo malware in two Go modules, gocommunity.io/orderedbtree and gogets.dev/btreex, and two Terraform providers, gocommunity-io/dockerd and kreuzwenker/docker. The providers are the first known case of an attacker using HashiCorp's Terraform Registry to distribute malware (The Hacker News). Graphalgo was first documented in February and has been attributed to North Korean operators. They approach developers with fake job offers and give them coding tests whose repositories install a malicious dependency.
kreuzwenker/docker is a typosquat of kreuzwerker/docker, a legitimate Docker provider with 56 million reported downloads. The fake provider had 1,449 downloads and gocommunity-io/dockerd had 222 (The Hacker News). Terraform downloads providers from the registry and runs them on whatever machine executes the plan, including CI runners that hold cloud credentials. Both malicious providers run their payload only when the SHA-256 hash of two Terraform values, containerName and networkID, matches a value hardcoded in the provider. On a match, the provider uses that hash as a key to decrypt a file path, extracts an archive named import-resource.sqlite3, and starts the payload as a detached go run . process (GBHackers).
The Go modules contain the same malware. The module gocommunity.io/orderedbtree, published August 11, contains the implant in plaintext. The module gogets.dev/btreex, published September 8, hides it in a ZIP archive disguised as a SQL file and runs it only when it receives an object with one specific integer price value. The attacker forged Git commit dates to make btreex look as if it dated from November 2025. The Go toolchain takes a version's date from its commit timestamp, so the Go module proxy and pkg.go.dev both displayed the false date. The attacker registered the two import domains, gocommunity[.]io and gogets[.]dev, each within a day of creating a matching GitHub organization. Both domains claimed to offer vanity import paths for Go packages, but neither gave outside developers a way to publish (GBHackers).
The second stage is a remote access trojan written in Go. It collects the operating system, architecture, hostname, username, home directory, hardware details and whether Node.js is installed, and sends that check-in to a Slack channel. It then takes commands from two places: a Slack workspace it polls every 10 seconds, and a smart contract on the Arbitrum Sepolia test network it polls every three seconds. Commands can run more Go or JavaScript code through Node.js, or remove the implant (GBHackers). Each infected host generates its own key pair, so one victim cannot read commands meant for another (The Hacker News).
Earlier the same week, researchers flagged a batch of npm packages that delivered the same malware, including indexed-btree, sort-btree, mathmain and graphlib-js. Some of them decrypt their payload only after the victim solves a linear system with one specific matrix (The Hacker News). Both sets use B-tree utility names: indexed-btree and sort-btree on npm, orderedbtree and btreex in Go. In mid-September, SentinelOne reported that TraderTraitor, another North Korea-linked cluster, used tampered Terraform lock files to deliver Rust backdoors from provider registries under its own control (The Hacker News).
Conditional triggers keep a payload inactive during automated analysis. The July npm package that only turned malicious when it was used, which I have already covered here, is another example.
The Go module mirror keeps its own copy of every version it fetches, so a module can remain downloadable from proxy.golang.org after its source repository is deleted. The mirror's index at index.golang.org records the time proxy.golang.org first cached each version, a first-seen date the module author cannot backdate (Go module mirror). The go command checks each download against the checksum database at sum.golang.org. A passing checksum means the bytes match the first copy recorded for that version, not that the code is safe.
Treat any version of the four packages as a compromise indicator. Search go.mod and go.sum files, Go module caches, internal module proxies, .terraform.lock.hcl files and CI build logs for the four names, and confirm that every Terraform configuration using the Docker provider sets its source to kreuzwerker/docker. On developer machines and build runners, review outbound connections to api.slack.com, public blockchain RPC endpoints and Arbitrum Sepolia where that traffic has no business reason (GBHackers). Rotate every credential reachable from a machine that ran either module or either provider.
Finding the Go modules in your builds
Gastropod proxies Go modules through the GOPROXY setting and records every pull: the module's PURL and sha256 digest, who pulled it, the client, the source IP and the time. For this campaign, you search that record for pkg:golang/gocommunity.io/orderedbtree and pkg:golang/gogets.dev/btreex at any version. The results list the developers and runners that fetched either module and when. Gastropod timestamps each pull itself, so the record shows when btreex was first fetched in your environment, regardless of the forged November 2025 commit dates. The dependency graph shows which internal services require either module, directly or through another module, and a block rule at intake keeps both module paths out of new builds. Unfortunately Gastropod does not proxy Terraform yet but it is getting higher on our list of priorities as we see these types of attacks happen so look for that soon.
Go clients use Gastropod through a GOPROXY setting (Go setup). The product page has screenshots of the audit record and dependency graph, and you can see the same record for your own module pulls from app.gastropod.io/start.
Sources