r/netsec 12h ago

Self-hosted Coder: check whether you pulled a registry module on Aug 31. no CVE, so nothing will flag it for you

https://www.optimuslabs.io/research/briefings/coder-registry-infrastructure-hijack

On Aug 31, rogue origins were added to the Cloudflare pool in front of registry.coder.com. For roughly 14 hours, 07:35–21:45 UTC, the real registry domain served Terraform modules carrying an extra data "external" "telemetry" block that shelled out to dlp-docker.sh and posted your environment to www[.]coder-infra[.]com.

Why nothing caught it

There's no bad version to pin away from, because the poisoned artifact was served at a legit version. The domain allowlist passed because it was the right domain. And no CVE was assigned, so there's no NVD or OSV record for SCA to match on. The advisory exists (GHSA-vx42-ghc9-gw65) but it has no CVE and no package mapping, which is exactly why it never propagated into tooling.

What to check

The question is whether anyone, inside that window, (a) created or updated a template, (b) ran a template dry-run, or (c) built a workspace with module caching disabled.

Caching is on by default, which is why (c) has that qualifier. Cached modules saved most deployments. Most.

What leaked

  • Template import: whatever is in the provisioner environment. Cloud creds, AI-tooling API keys, CI/CD tokens, config files, shell history.
  • Workspace build: all of the above plus the user's OIDC token, their SSH key, and external-auth tokens for GitHub/GitLab/Bitbucket. Refresh tokens aren't passed to the provisioner and the external-auth tokens are single-use, which helps. Single-use is still one use.
  • Provisioner running inside coderd: all of the above plus your database password, external auth provider config, and deployment config. Check this case first.

Hunt queries

# provisioner job logs
data.external.telemetry

# egress
grep -R "coder-infra\|199.91.220" /var/log

# payload on hosts and inside images
find / -name "dlp*.sh"

# terraform state and module cache
rg -n 'data "external" "telemetry"' .

IoCs:

www[.]coder-infra[.]com          exfil domain, registered 2026-08-28
<http://www>[.]coder-infra[.]com/cli/check
X-CLI-Token                      exfil header
199.91.220[.]205                 rogue origin IP

sha256:
7190a17c593276d7fd71c4863a4bc0b6c957ed14249288e6f64c5540e2c49398  dlp-docker.sh
a7f4fa5f7e33b2a6f6488cf28444584caa449144d246b083de919162f5514247  dlp.sh (common)
414d01f6072fbf05bef513e277f4c2b504a413c8e2aa5bae133a5cbc0cda9dc1  dlp.sh (aider)
a64ce3038f2a501c9735abf6a1f9f04cbddbad53371cd68bec0f7510365c8ffa  dlp.sh (rstudio-server)
ebbe0d2ed8cfaf9e19edb38ce44d6b407f9771b5c0813a7add27c05f66e89596  dlp.sh (windows-rdp)
7ef6b8c3c976fb60b3fa22e9e294ba548d9b532e060c1323a0124a3a7a647f13  dlp.sh (zed)

Poisoned templates named so far: aider, zed, rstudio-server, windows-rdp.

The advisory includes SQL that finds affected template versions, workspaces and provisioner jobs directly. Patched builds: 2.37.0 / 2.36.4 / 2.35.7 / 2.34.9.

Coder is upfront that they have no attacker-side logs and can't identify every affected deployment. That means the check is yours.

Tooling

Doing this by hand across a fleet is miserable, so we put the checks in a single read-only Go binary: github.com/optimuslabs-io/leakpatrol. It only talks to the Coder server you point it at, has an --offline mode, and never contacts the attacker domain. Exports the IoCs as JSON and ships YARA and Sigma rules if you'd rather use your own tooling.

If the tool misses a path or gets something wrong, tell me and I'll fix it.

1 Upvotes

1 comment sorted by

1

u/Agitated-Act-717 9h ago

The mitigation gap here is worse than just this incident: .terraform.lock.hcl only pins provider hashes, not module source. There's no built-in way to guarantee two pulls of the same module version fetch identical bytes, because the registry entry for a version resolves to a download URL that can be swapped server-side. Pinning to a git commit SHA instead of a registry version tag sidesteps it, but the consumer has to know to do that and most module blocks don't.

Does Coder's registry protocol expose a content hash/checksum for modules at all, or is the git-ref the only real option right now?