Back to Optimus Labs
>leakpatrol
INCIDENT · registry.coder.com hijack · no CVE

Tells a Coder operator whether their deployment was exposed to the Aug 31 registry hijack, through which path, and therefore exactly what to rotate.

read-only · zero dependencies · one static binary · --offline

window: 2026-08-31 07:35 → 21:45 UTC advisory: GHSA-vx42-ghc9-gw65 · CVSS 9.0 platforms: macOS · Linux · Windows · Kubernetes

why this exists

No CVE. No bad version. Nothing for a scanner to match.

For about fourteen hours on August 31, 2026, an attacker controlled Coder's Cloudflare pool for registry.coder.com and served tampered Terraform modules carrying a credential harvester. Any template create, update or dry-run, and any workspace build with module caching disabled, pulled it. It ran inside the provisioner and shipped cloud, AI-tooling, CI/CD, Git and SSH credentials to a lookalike domain registered three days earlier.

Version pinning pins the version number, not the bytes. Domain allowlisting saw the hostname you are required to trust. Nothing new was published, so cooldowns and quarantines had nothing to hold, and software-composition analysis had no known-bad version to match.

Exposure is scoped by what your deployment did in the window, not by the version it runs. leakpatrol reads that evidence (your provisioner jobs, module caches, images and egress logs) and tells you which path applies and what to rotate.

Six tiers, run from wherever you have access

leakpatrol all runs a preflight, then every tier it can reach, judges the verdict on the tiers that ran, and names the ones that did not. You do not need all six, and a missing one is coverage, not a clean bill of health.

deploy: your Coder server's API

Template versions and workspace builds whose provisioner jobs ran in the window, plus the data.external.telemetry sentinel in their logs. Needs a session token or an existing coder login. The universal tier: any OS, nothing installed.

db: Coder's verbatim advisory SQL

Queries 1 to 3 from the advisory: module-cache entries in the window, workspaces on those versions, the sentinel in job logs. Authoritative. No psql on that machine? --print-only prints the SQL for any client.

fs: provisioner hosts, pods and workspaces

The harvester by SHA-256 and by name, the Terraform data "external" "telemetry" block, and exfil indicators in shell histories and module caches. Copy the binary to the provisioner and run fs / there.

image: container images, layer by layer

Reads a docker save / podman / nerdctl / crane / skopeo tar, a reference, or stdin, without extracting anything.

logs: proof that data actually left

Firewall, proxy, DNS and VPC-flow exports in any format, plain or gzipped, files or stdin: the exfil domain, IP, URL path and header. This is the tier that turns EXPOSED into COMPROMISED.

needs retention back to Aug 31, 07:35 UTC

coder-version: informational

Your coder CLI against the patched builds: 2.37.0, 2.36.4, 2.35.7, 2.34.9. Upgrading stops future exposure; it does not un-harvest credentials already taken.

Preserve before you purge. The cached module tarballs, provisioner job logs and egress logs are the evidence of what happened, and Coder's cleanup transaction deletes the very rows the advisory queries return. Export them to storage you control first. leakpatrol db --purge prints that transaction and never runs it.

A verdict scoped to what it could actually see

Every finding names the artifact behind it: the in-window job, the cached module row, the file hash, the egress line. The exposure path on each finding drives the rotation list, and every run prints what it could not read.

Four verdicts, one order

CLEAN · INDETERMINATE · EXPOSED · COMPROMISED. COMPROMISED requires proof it ran or that data left: the sentinel in a job log, an exfil call in a shell history, an egress hit. Pulled-or-present with no proof of execution is EXPOSED. Act as if it ran.

Skips are coverage, not verdict

Only a missing deploy degrades the verdict. A laptop without psql, an image tar or a flow-log export still earns an honest CLEAN with a COVERAGE table under it, naming what only the database could have seen.

Missing logs are not negatives

Coder drops provisioner logs, so an in-window job whose log is gone stays EXPOSED and is flagged execution cannot be excluded. In-window checks pad ±15 minutes for provisioner clock skew.

Rotation, ordered

The exposure path decides the list: template-import means the provisioner's own environment, workspace-build adds each owner's OIDC token, SSH key and external-auth tokens, and present / executed / egress mean everything on that host. Cloud keys go first.

leakpatrol all: every tier it can reach --json: the full record, | jq .verdict --offline: no network connection at all

Progress narrates on stderr (green ✓ only for a genuine empty result, red ! for hits, yellow ✗ for a tier that failed), and the report goes to stdout. An explicit command that cannot run is a tool error, never a verdict.

Guarantees enforced by leakpatrol

You are pointing this at a provisioner that may already be compromised, and at production credentials. Each promise below is something you can check yourself, not something to take on faith.

Read-only, always

It reads deployments, databases, filesystems, images and logs. It never writes to them. db --purge and db --print-only print SQL for you to run; this tool does not execute a destructive statement.

Zero third-party dependencies

Standard library only, one static binary, CGO_ENABLED=0. A tool that hunts an unaudited supply chain should not add one of its own. It runs unchanged in Coder's own alpine image.

--offline means no network at all

For air-gapped forensics and for hosts you do not want talking to anything. No telemetry and no update ping in any mode: the only connections it ever makes are the Coder API and database you point it at yourself.

Nothing it finds is executed

The harvester scripts are matched by SHA-256, by name and by the Terraform block that invokes them. They are never run, and container images are read layer by layer without extraction.

A degraded scan never reports CLEAN

If the deployment was not asked, or something material could not be read, the verdict is INDETERMINATE and the report names the gap. CLEAN states only that the tiers that ran found none of the published indicators.

Provenance you can prove

Release binaries carry sigstore provenance, so you can prove one was built by this repository's release workflow before running it on a host that matters. Apache-2.0, and the analysis behind it is published in full.

Read-only is not authorisation. deploy reads a Coder deployment with a session token and db reads its database. Run them only against systems you own or have written authorisation to inspect.

Install & verify

Three ways in, ranked for trust. The binary you carry onto a provisioner is the first thing that should be verifiable, so every path below proves what it installs before it installs it.

go install

hermetic

Preferred: builds from source through the Go module proxy. Stdlib-only, so no third-party modules are fetched.

go install github.com/optimuslabs-io/leakpatrol/cmd/leakpatrol@latest

needs the Go toolchain

Verified binary

provable

Download a release asset for linux/darwin × amd64/arm64 or windows/amd64, then prove provenance with sigstore before you run it.

gh attestation verify leakpatrol_* -R optimuslabs-io/leakpatrol

releases · checked against SHA256SUMS

Installer

in a hurry

For when you need the binary on a host now. It checks the download against SHA256SUMS, which catches corruption but not a compromised release, so read it first: it is short and lives at a stable path for that reason.

curl -fsSL https://raw.githubusercontent.com/optimuslabs-io/leakpatrol/main/install.sh | sh

read the script before you pipe it

then run it, wherever your access is

One command on the deployment, or just your logs

With a coder login on the machine, all reaches your deployment automatically; with CODER_PG_CONNECTION_URL and psql it also runs Coder's advisory SQL. If this machine is not the provisioner, copy the binary there and run fs /.

leakpatrol all
kubectl exec -n coder coder-provisioner-0 -- /tmp/leakpatrol fs / --json
zcat fw-2026-08-31*.log.gz | leakpatrol logs -
docker save myregistry/coder-provisioner:latest | leakpatrol image -

Also: leakpatrol iocs prints every indicator as JSON, leakpatrol db --print-only prints Coder's advisory SQL for any client, and --json | jq -r .verdict is the fleet-friendly form. Flags go before files.

Questions about the Coder registry hijack or leakpatrol

Was my Coder deployment affected by the registry hijack?

Exposure is scoped by what you did during the window (07:35–21:45 UTC, Aug 31, 2026), not by the version you run. Any template create, update or dry-run, or any workspace build with module caching disabled, pulled from registry.coder.com during those fourteen hours. leakpatrol answers it from your own deployment: in-window provisioner jobs, the harvester on disk or in an image, the sentinel in job logs, and exfiltration in your egress logs.

There is no CVE. What do I scan for?

Nothing your vulnerability feed carries. This was a delivery-infrastructure compromise, not a code flaw: no CVE, no NVD or OSV entry, no bad version to pin away from, and the malicious bytes arrived from the correct hostname under a version number you had already approved. The only evidence lives in your own provisioner jobs, module caches, container images and egress logs.

Does upgrading to a patched Coder build fix it?

Upgrade to 2.37.0, 2.36.4, 2.35.7 or 2.34.9, but understand what it does: it stops future exposure. It does not un-harvest credentials already taken. A patched deployment that pulled a module in the window is exposed; an unpatched deployment that pulled nothing is not.

What does leakpatrol actually check?

Six tiers. deploy reads your Coder server's API for in-window template versions and workspace builds and hunts the data.external.telemetry sentinel in their provisioner logs. db runs Coder's verbatim advisory SQL. fs scans provisioner hosts, pods and workspaces for the harvester by SHA-256 and the Terraform external telemetry block. image scans container image tars layer by layer without extracting. logs searches firewall, proxy, DNS and VPC-flow exports for the exfil domain, IP, path and header. coder-version compares the CLI against the patched builds. leakpatrol all runs every tier it can and names the ones it could not.

What do the verdicts mean?

COMPROMISED: the harvester ran or data left. Rotate now. EXPOSED: the tampered module was pulled or is present with no proof it ran. Treat it as if it ran. INDETERMINATE: the deployment itself was not asked, or something material could not be read. CLEAN: the tiers that ran found none of the published indicators and nothing material was missing. Skips are coverage, not a verdict, and only a missing deploy degrades it.

Which credentials do I rotate first?

The exposure path on each finding drives the list. template-import exposes the provisioner's own environment: AWS, GCP and Azure keys, ANTHROPIC_API_KEY and OPENAI_API_KEY, CI/CD and registry credentials, Terraform variables, plus the Coder database password if the provisioner runs inside coderd. workspace-build adds each affected owner's OIDC token, SSH key, and GitHub, GitLab or Bitbucket external-auth tokens. Cloud keys first: they unlock more than any single model or app.

Can I run leakpatrol without access to the Coder deployment?

Yes. An analyst with only log exports can pipe them in: zcat fw-2026-08-31*.log.gz | leakpatrol logs -. An operator with a database but no psql on that machine can run leakpatrol db --print-only and paste the SQL into any client. Filesystem and image scans need no Coder credentials at all.

Is leakpatrol safe to run on a possibly-compromised host?

That is the design target. One static binary, zero third-party dependencies, read-only, and it never executes anything it finds. --offline guarantees no network connection at all. It runs unchanged in Coder's own alpine image, so you can copy it into a provisioner pod. Release binaries carry sigstore provenance: verify with gh attestation verify before you run one anywhere that matters.

Should I purge the cached modules right away?

Preserve before you purge. The cached module tarballs, provisioner job logs and egress logs are the evidence of what happened, and the purge SQL deletes the very rows the advisory queries return. If this becomes a breach notification, a regulatory inquiry, an insurance claim or a legal hold, you will be asked for them. Export to storage you control first, then clear caches, recycle provisioner pods and upgrade.

Who built leakpatrol?

Civilizations, the threat research team at Optimus Labs, the agent security company. It is free and open source under Apache-2.0, and it implements Coder's advisory. Optimus Labs is not affiliated with, endorsed by, or acting for Coder Technologies, Inc.; Coder is their trademark, used here only to name the product this tool inspects. The authoritative source on the incident is Coder's advisory.