Skip to content

Build1 publisher2 min readPublished

Graphalgo malware in a Terraform provider stays inert until two variables hash to one digest

Aikido found the Graphalgo campaign's Go port inside two Terraform providers, one of them a typosquat of kreuzwerker/docker. The payload decrypts only when containerName and networkID hash to a hardcoded SHA256.

The Engineer · Build desk

Illustration accompanying Graphalgo malware in a Terraform provider stays inert until two variables hash to one digest

What happened

  • Aikido said it found Go malware in at least two Terraform providers and two Go modules, and that it has not previously observed malware distributed through Terraform providers.
  • The two providers, published in early September, are gocommunity-io/dockerd and kreuzwenker/docker, the second a typosquat of kreuzwerker/docker, which Aikido reports at 56 million downloads.
  • Both hide entry points in resource_docker_container_funcs.go that activate only when the SHA256 of the containerName and networkID Terraform variables, concatenated, matches one hardcoded digest.
  • Once the hash matches, the code unzips a bundled archive, AES-decrypts each file inside it and executes the result as a Go package using a detached go run . command.
  • The second stage is a Go remote-access agent that runs two command-and-control channels at once, one through a Slack bot token and one through blockchain dead drops.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • exposure A pipeline that resolved the typosquatted name holds the code on disk, and quiet logs do not tell its owner whether the gated branch ever fired.
  • cost Ruling out execution means checking historical containerName and networkID values against a single digest, and that work lands on whoever keeps the plan logs.
  • decision Shops that treat Terraform providers and Go modules as configuration rather than dependencies have to decide whether those resolvers get the name-similarity review they already run on npm.
  • precedent Aikido reports the operator built at least two fake Go ecosystems to promote its modules, so further Go-side packages from the same campaign are the reasonable expectation.

The operator has to know two values in the target's configuration before any of this runs. The digest those two variables must produce is also the AES key that decrypts the path to the payload inside the package [10][12]. A reviewer who pulls the provider source gets the hash and the ciphertext. Getting the plaintext means guessing the two strings.

So dynamic analysis returns nothing useful. Run the provider with your own container name and network ID and it behaves like the provider it imitates. Aikido describes it as inert except under specific runtime conditions and takes that as evidence of targeted delivery [11].

The payload ships as examples/resources/docker_container/import-resource.sqlite3 [14]. A SQLite file in an examples directory is among the least-read artefacts in a provider repo, which is presumably the point. Execution needs a Go toolchain on the host, because go run compiles before it runs, and the launch is detached, so the second stage need not end with the process that started it [13].

Aikido places the hook in the resource functions for docker_container and conditions it on variable values [10]. The post does not say which Terraform command runs it. Resolving the name puts the code on the runner; running it needs a plan in which those two variables carry the operator's exact strings. The check available to a defender is therefore which namespace the pipeline resolved, not what the code did at runtime.

The second stage talks to api.slack.com, and the workspace subdomain named in Aikido's writeup is never contacted directly [17]. A hostname blocklist assembled from portfolio-devs.slack.com will not see the traffic. Check-in goes to a channel called frontend-devs, later traffic moves to a second workspace and a channel named qa-announcements [17][19], and the other channel reads an Ethereum contract at 0xAD02b5cDE693529d3bdA0266299501ad0193036C on the Arbitrum Sepolia testnet [20]. Each client derives a shared key from its own ephemeral pair and two threat actor public keys, so hosts posting into the same Slack channel cannot read each other's messages [18].

The first system report includes whether node is on the path [16]. Aikido ties the samples to the Graphalgo NPM campaign that ReversingLabs first reported in February 2026, and describes the Go code as a port sharing blockchain and Slack infrastructure and a public key with recent JavaScript samples distributed via NPM [5][6]. Four packages are named so far, two providers and two Go modules [2], and the malicious provider namespace kreuzwenker differs from kreuzwerker by a single letter [1].

What to watch

  • Whether the Terraform Registry publishes removal notices or download counts for kreuzwenker/docker and gocommunity-io/dockerd.
  • Whether anyone recovers the containerName and networkID preimage of the trigger digest, which would name the intended target.
  • Whether the same Go port shows up in providers from other namespaces or in further modules under the actor's fake Go ecosystems.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories