Coder Module Registry Supply-Chain Attack — GHSA-vx42-ghc9-gw65
Executive Summary
On 2026-08-31, between 07:35 and 21:45 UTC, an attacker who had compromised a Cloudflare API key added unauthorized origin server(s) — including malicious IP 199.91.220.205 — to the Cloudflare origin pool backing registry.coder.com, Coder's official Terraform module registry. For that window, a subset of registry traffic was silently routed to attacker infrastructure instead of Coder's legitimate servers. The rogue origin served tampered Terraform modules that invoked a malicious script (dlp.sh / dlp-docker.sh) via a Terraform data "external" block during provisioning.
The script harvested cloud credentials (AWS/GCP/Azure), AI-tooling API keys, CI/CD tokens, configuration-file secrets, terminal history, user OIDC tokens, SSH keys, external-auth tokens, and — when the provisioner ran inside coderd — the Coder database password, exfiltrating everything to the lookalike domain coder-infra.com (registered 2026-08-28) via http://www.coder-infra.com/cli/check with header X-CLI-Token: your-secret-token. Coder identified and fully remediated the incident the same day and shipped patched releases 2.37.0, 2.36.4, 2.35.7, and 2.34.9. GitHub Advisory GHSA-vx42-ghc9-gw65 rates the issue Critical, CVSS v4 9.0/10.
This is a supply-chain / CI-CD / developer-endpoint event, not classic malware: the highest-value detections are (1) exact-IOC network/DNS matches to the exfil domain and IP, (2) execution of the named DLP scripts, (3) anomalous shell children of Terraform/Coder provisioning processes, and (4) credential-file access clustering immediately after a provisioning run. Falcon endpoint telemetry covers items 1–4 well; the Cloudflare control-plane compromise itself and Coder's internal database exposure sit outside endpoint visibility and are called out as explicit coverage gaps in §15.
Defender priority: Patch every Coder deployment (2.37.0/2.36.4/2.35.7/2.34.9), block egress to coder-infra.com and 199.91.220.205, run the §7 native SQL sweep to identify affected templates/workspaces, and rotate every credential class the provisioner could have touched — these steps close the loop even for stores that were exposed before this pack existed.
Source Review & Web Hunter Notes
All five mandated sources were fetched live on 2026-09-08 and saved verbatim to Coder-Registry-SupplyChain-Hunt-sources/ before any fact in this pack was written. Every atomic IOC shipped in this pack is corroborated by at least two independently-authored sources. All fetched content was reviewed for injected/adversarial instructions directed at an AI agent — none were found — and analyzed strictly as data.
| Tier | Source | Key Finding | Carry Forward |
|---|---|---|---|
| T1 | Coder — Registry Security Incident postmortem (coder.com/blog, 2026-09-04) | Official timeline (07:35–21:45 UTC, Aug 31), confirms Cloudflare API key compromise, coder-infra.com as the lookalike domain, points to GH advisory for IOCs/remediation. | Yes |
| T1 | GitHub Security Advisory GHSA-vx42-ghc9-gw65 (coder/coder, published 2026-09-01) | Full IOC table (domain, IP, URL, header, file hashes for dlp.sh/dlp-docker.sh variants, Terraform data.external.telemetry block), CVSS v4 9.0, remediation SQL, patched versions, exposure scenarios. | Yes |
| T2 | BleepingComputer, Bill Toulas, 2026-09-03 | Corroborates timeline, exfil domain, targeted secret categories, notes Coder's customer base and that refresh tokens were not exposed. | Yes |
| T2 | Cybersecuritynews.com, Abinaya, 2026-09-08 | Corroborates dlp-docker.sh via data.external.telemetry, the X-CLI-Token header, IP 199.91.220.205, and hunting guidance (DNS/proxy/VPC flow log review). | Yes |
| T2 | GBHackers, Divya, 2026-09-08 | Corroborates both script names (dlp.sh and dlp-docker.sh explicitly), full IOC table incl. subdomain www.coder-infra.com, notes domain registered 2026-08-28. | Yes |
Cross-reference notes: Domain coder-infra.com and IP 199.91.220.205 are each corroborated by three-plus independent sources; the X-CLI-Token header and data.external.telemetry construct are corroborated by three sources. No source suggested a different value for any atomic IOC, so no substitution risk applies.
Safety note: all five fetches returned ordinary editorial/vendor content. No page contained text directed at "the AI agent," no authorization claims, and no embedded commands — nothing was excluded on injection grounds.
Hunt Brief & Attack Chain
Hunt hypotheses (ordered by fidelity)
| # | Hypothesis | MITRE | Falcon Telemetry | Expected FP | Confidence |
|---|---|---|---|---|---|
| H1 | Endpoints resolve/connect to coder-infra.com (or www subdomain) or IP 199.91.220.205. | T1071.001, T1567 | DnsRequest, NetworkConnectIP4 | None expected — purpose-built lookalike domain | High |
| H2 | A process named dlp.sh or dlp-docker.sh executes anywhere in the fleet. | T1195.002, T1059.004 | ProcessRollup2 / SyntheticProcessRollup2 | Rare internal script name collision | High |
| H3 | A Terraform / terraform-provider process spawns a shell interpreter (external data source execution). | T1195.002, T1059.004 | ProcessRollup2 | Legitimate local-exec / external-data-source provisioners | Medium |
| H4 | Terraform/Coder provisioner or its shell children spawn curl/wget toward non-allowlisted destinations. | T1071.001, T1567 | ProcessRollup2 | Legitimate module/package/container downloads | Medium |
| H5 | Clustered access to SSH keys, cloud credential files, .netrc, or OIDC caches shortly after a provisioning run. | T1552.001, T1552.004, T1555 | ProcessRollup2 (command-line evidence) | Legitimate developer/admin troubleshooting | Medium |
| H6 | Coder provisioner_job_logs contain the sentinel string data.external.telemetry. | T1195.002 | Coder application log / DB (native, non-CQL) | Legitimate use of a differently-named external data source — low | High |
| H7 | DNS requests to newly-observed Coder-themed lookalike/typosquat domains beyond the known IOC. | T1583.001, T1071.001 | DnsRequest | Legitimate third-party domains containing "coder" | Low |
| H8 | Outbound HTTP requests to path /cli/check carrying header X-CLI-Token. | T1071.001, T1567 | Proxy/L7 log (not standard Falcon sensor telemetry) | None expected, but field names are tenant-specific | Medium |
Attack chain
| Step | Telemetry | Hunt Angle |
|---|---|---|
| 1. Attacker compromises a Cloudflare API key (outside victim visibility) | Cloudflare audit log (Coder-side) | Not victim-huntable; noted for context only |
| 2. Attacker adds unauthorized IP (199.91.220.205) to the registry.coder.com Cloudflare origin pool | Cloudflare audit log (Coder-side) | Not victim-huntable; noted for context only |
| 3. Victim's Terraform/Coder provisioner pulls a tampered module from registry.coder.com during the incident window | Network/DNS logs, Coder provisioner_job_logs | H1, H6 |
4. Terraform data "external" "telemetry" block executes dlp.sh / dlp-docker.sh | ProcessRollup2 | H2, H3, H6 |
| 5. Script harvests cloud/AI/CI-CD/SSH/OIDC/config secrets from the provisioner host (and coderd DB password if co-located) | ProcessRollup2 command-line evidence | H5 |
| 6. Script exfiltrates via HTTP to coder-infra.com/cli/check with header X-CLI-Token | DnsRequest, NetworkConnectIP4, proxy/L7 log | H1, H4, H8 |
| 7. (Downstream, outside incident window) Attacker may reuse stolen cloud/CI-CD credentials for follow-on access | CloudTrail / Cloud Audit Log / CI-CD audit log | Out of scope for this pack — recommend a dedicated cloud-IAM hunt if credentials were not rotated in time |
Consolidated IOC Table
| Type | Value | Confidence | Action | Context | Expiry |
|---|---|---|---|---|---|
| Domain | coder-infra.com | High (5 sources) | detect | Lookalike exfil domain, registered 2026-08-28 | 2027-03-08 |
| Domain | www.coder-infra.com | High (2 sources) | detect | Lookalike exfil domain, www subdomain | 2027-03-08 |
| IPv4 | 199.91.220.205 | High (3 sources) | detect | Unauthorized Cloudflare origin IP / exfil destination | 2027-03-08 |
| URL | http://www.coder-infra.com/cli/check | High (3 sources) | detect | Exfiltration callback path | 2027-03-08 |
| HTTP Header | X-CLI-Token: your-secret-token | High (3 sources) | hunt | Required header gating the exfil callback | 2027-03-08 |
| Filename | dlp.sh | High (3 sources) | detect | DLP payload script, common variant | 2027-03-08 |
| Filename | dlp-docker.sh | High (3 sources) | detect | DLP payload script, Docker/container variant | 2027-03-08 |
| SHA-256 | 7190a17c593276d7fd71c4863a4bc0b6c957ed14249288e6f64c5540e2c49398 | High (1 source) | prevent | dlp-docker.sh sample | 2027-03-08 |
| SHA-256 | a7f4fa5f7e33b2a6f6488cf28444584caa449144d246b083de919162f5514247 | High (1 source) | prevent | dlp.sh sample (common variant) | 2027-03-08 |
| SHA-256 | 414d01f6072fbf05bef513e277f4c2b504a413c8e2aa5bae133a5cbc0cda9dc1 | High (1 source) | prevent | dlp.sh sample (aider variant) | 2027-03-08 |
| SHA-256 | a64ce3038f2a501c9735abf6a1f9f04cbddbad53371cd68bec0f7510365c8ffa | High (1 source) | prevent | dlp.sh sample (rstudio-server variant) | 2027-03-08 |
| SHA-256 | ebbe0d2ed8cfaf9e19edb38ce44d6b407f9771b5c0813a7add27c05f66e89596 | High (1 source) | prevent | dlp.sh sample (windows-rdp variant) | 2027-03-08 |
| SHA-256 | 7ef6b8c3c976fb60b3fa22e9e294ba548d9b532e060c1323a0124a3a7a647f13 | High (1 source) | prevent | dlp.sh sample (zed variant) | 2027-03-08 |
| Behavioral (Terraform construct) | data.external.telemetry | High (3 sources) | hunt | Exploitation trigger — external data-source block name | 2027-03-08 |
| Advisory ID | GHSA-vx42-ghc9-gw65 | High (5 sources) | reference | GitHub Security Advisory tracking this incident | — |
Affected Surface & Telemetry Matrix
| Surface | Required Telemetry | Priority | Gap Risk |
|---|---|---|---|
| Linux/macOS dev & CI runner hosts running Terraform/Coder provisioner | Falcon sensor: ProcessRollup2, SyntheticProcessRollup2, NetworkConnectIP4, DnsRequest | Critical | Low — native Falcon sensor coverage |
Coder control plane (coderd) application/DB layer | provisioner_job_logs, DB audit (native, non-Falcon) | Critical | GAP — outside Falcon endpoint telemetry; use §7 native hunts |
| Cloud provider control planes (AWS/GCP/Azure) | CloudTrail, GCP Cloud Audit Logs, Azure Activity Log | High | GAP — requires separate cloud-log ingestion, not covered by endpoint sensor |
| Network egress / web proxy | Proxy/firewall L7 logs with URL + header visibility | Medium | GAP — HTTP header-level match (X-CLI-Token) needs proxy log ingestion into Falcon NG-SIEM; field names are tenant-specific |
| Cloudflare control plane (Coder-owned, informational) | Cloudflare audit log | Informational | GAP — out of victim-org control; no action beyond awareness |
ATT&CK Mapping
| Tactic | Technique | Technique Name | Observed Behavior |
|---|---|---|---|
| Resource Development | T1583.001 | Acquire Infrastructure: Domains | coder-infra.com registered 2026-08-28, three days before the pivot |
| Initial Access | T1195.002 | Supply Chain Compromise: Compromise Software Supply Chain | Tampered Terraform modules served from a compromised registry origin |
| Execution | T1059.004 | Command and Scripting Interpreter: Unix Shell | Terraform data "external" block invokes dlp.sh / dlp-docker.sh |
| Credential Access | T1552.001 | Unsecured Credentials: Credentials In Files | Cloud credential files, .netrc, config-file secrets, terminal history |
| Credential Access | T1552.004 | Unsecured Credentials: Private Keys | SSH key harvesting |
| Credential Access | T1528 | Steal Application Access Token | User OIDC tokens and external-auth tokens exfiltrated |
| Command and Control | T1071.001 | Application Layer Protocol: Web Protocols | HTTP callback to coder-infra.com/cli/check with X-CLI-Token header |
| Exfiltration | T1567 | Exfiltration Over Web Service | Harvested secrets sent to attacker-controlled web endpoint |
Native Audit-Log Hunts
These checks run directly against a Coder deployment's own database/logs, reproduced verbatim from GitHub Advisory GHSA-vx42-ghc9-gw65 (source snapshot 02-github-advisory.txt). They cover the coderd/DB surface that Falcon endpoint telemetry cannot see (§5 gap). Detection/hunt SQL only — no destructive statement is included here; the purge statement lives in the §13 Playbook B runbook.
All templates that used a module fetched during the incident window
SELECT
t.name AS template,
tv.name AS template_version,
tv.id AS template_version_id,
f.id AS module_file_id,
f.created_at AS module_cached_at,
tv.created_at AS version_created_at
FROM files f
JOIN template_version_terraform_values tvtv
ON tvtv.cached_module_files = f.id
JOIN template_versions tv
ON tv.id = tvtv.template_version_id
JOIN templates t
ON t.id = tv.template_id
WHERE f.created_by = '00000000-0000-0000-0000-000000000000'
AND f.mimetype = 'application/x-tar'
AND f.created_at >= '2026-08-31 07:35:00+00'
AND f.created_at < '2026-08-31 21:45:00+00'
ORDER BY t.name, tv.created_at;
All workspaces that used a module fetched during the incident window
SELECT
w.name AS workspace,
u.username AS owner,
wlb.transition,
wlb.job_status,
wlb.created_at
FROM workspace_latest_builds wlb
JOIN workspaces w ON w.id = wlb.workspace_id
JOIN users u ON u.id = w.owner_id
WHERE wlb.template_version_id IN (
SELECT tvtv.template_version_id
FROM files f
JOIN template_version_terraform_values tvtv
ON tvtv.cached_module_files = f.id
WHERE f.created_by = '00000000-0000-0000-0000-000000000000'
AND f.mimetype = 'application/x-tar'
AND f.created_at >= '2026-08-31 07:35:00+00'
AND f.created_at < '2026-08-31 21:45:00+00'
)
ORDER BY w.name;
Provisioner job logs containing the sentinel string
-- name: SearchLogsForKeyPhrase -- Search any provisioner job logs for the sentinel string, this will be a slow query SELECT DISTINCT pj.type AS job_type, pj.id AS job_id, wb.id AS workspace_build_id, w.id AS workspace_id, COALESCE(tv_import.id, tv_build.id) AS template_version_id, t.id AS template_id, t.name AS template, COALESCE(tv_import.name, tv_build.name) AS template_version, w.name AS workspace, u.username AS owner_or_initiator, pj.started_at, pj.job_status, w.deleted AS workspace_deleted FROM provisioner_job_logs pjl JOIN provisioner_jobs pj ON pj.id = pjl.job_id LEFT JOIN template_versions tv_import ON tv_import.job_id = pj.id LEFT JOIN workspace_builds wb ON wb.job_id = pj.id LEFT JOIN template_versions tv_build ON tv_build.id = wb.template_version_id LEFT JOIN workspaces w ON w.id = wb.workspace_id LEFT JOIN templates t ON t.id = COALESCE(tv_import.template_id, tv_build.template_id, w.template_id) LEFT JOIN users u ON u.id = COALESCE(w.owner_id, pj.initiator_id) WHERE pjl.output LIKE '%data.external.telemetry%' ORDER BY pj.started_at;
A hit on data.external.telemetry inside provisioner_job_logs, or a non-empty row set from either SELECT above, indicates this deployment pulled a tampered module during the incident window — treat as a compromise indicator requiring the §14 containment runbook, not just a tuning note.
CrowdStrike LogScale CQL Hunt Queries
Looks for: a DNS resolution or network connection to the Coder registry supply-chain exfil domain or IP. FP: none expected — coder-infra.com is a purpose-registered lookalike domain with no legitimate use.
// HUNT: DNS Resolution or Network Connection to Coder Registry Supply-Chain Exfil Domain/IP // MITRE: T1071.001, T1567, T1195.002 // CONF: high FP: low COST: low // REQUIRES: DnsRequest, NetworkConnectIP4 // FALSE POSITIVES: none expected -- coder-infra.com is a purpose-registered lookalike domain with no legitimate use // LOOKBACK: 90d (domain registered 2026-08-28; incident window 2026-08-31 07:35-21:45 UTC -- widened to catch staging/reuse) #event_simpleName=/^(DnsRequest|NetworkConnectIP4)$/ | DomainName=/^(www\.)?coder-infra\.com$/i or RemoteAddressIP4="199.91.220.205" | table([@timestamp, aid, ComputerName, UserName, #event_simpleName, DomainName, RemoteAddressIP4, RemotePort, ContextBaseFileName, CommandLine]) | sort(@timestamp, order=desc)
Looks for: execution of a process named exactly dlp.sh or dlp-docker.sh anywhere in the fleet. FP: internal tooling coincidentally named dlp.sh/dlp-docker.sh — rare.
// HUNT: Execution of Coder Registry Supply-Chain DLP Payload Scripts (dlp.sh / dlp-docker.sh) // MITRE: T1195.002, T1059.004 // CONF: high FP: low COST: low // REQUIRES: ProcessRollup2, SyntheticProcessRollup2 // FALSE POSITIVES: internal tooling coincidentally named dlp.sh/dlp-docker.sh -- rare // LOOKBACK: 90d #event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | FileName=/^dlp(-docker)?\.sh$/i | table([@timestamp, aid, ComputerName, UserName, FileName, ParentBaseFileName, CommandLine, SHA256HashData]) | sort(@timestamp, order=desc)
Looks for: a Terraform / terraform-provider process spawning a shell interpreter — the external-data-source execution step. FP: legitimate Terraform providers routinely shell out for local-exec / external data source lookups.
// HUNT: Terraform / Coder Provisioner Spawning Shell Interpreter (External Data Source Execution) // MITRE: T1195.002, T1059.004 // CONF: medium FP: medium COST: low // REQUIRES: ProcessRollup2 // FALSE POSITIVES: legitimate Terraform providers routinely shell out for local-exec / external data source lookups // TUNING: allowlist known-good external-data-source script paths/hashes; for retro hunts scope to 2026-08-31 07:35-21:45 UTC to cut noise // LOOKBACK: 90d #event_simpleName=/^ProcessRollup2$/ | ParentBaseFileName=/^terraform(-provider.*)?(\.exe)?$/i | FileName=/^(sh|bash|dash|ksh)(\.exe)?$/i | table([@timestamp, aid, ComputerName, UserName, ParentBaseFileName, FileName, CommandLine]) | sort(@timestamp, order=desc)
Looks for: a Coder CLI / Terraform provisioner workflow spawning curl or wget. FP: legitimate modules routinely fetch installers/container images via curl/wget.
// HUNT: Coder CLI / Terraform Provisioner Workflow Spawning Outbound Network Utility // MITRE: T1071.001, T1567 // CONF: medium FP: medium COST: medium // REQUIRES: ProcessRollup2 // FALSE POSITIVES: legitimate modules routinely fetch installers/container images via curl/wget // TUNING: pair with Q1's domain/IP match, or restrict to destinations outside an allowlisted registry/mirror set // LOOKBACK: 90d #event_simpleName=/^ProcessRollup2$/ | ParentBaseFileName=/^(terraform(-provider.*)?|coder|dlp(-docker)?\.sh|sh|bash)(\.exe)?$/i | FileName=/^(curl|wget)(\.exe)?$/i | table([@timestamp, aid, ComputerName, UserName, ParentBaseFileName, FileName, CommandLine]) | sort(@timestamp, order=desc)
Looks for: rapid credential/secrets-file access commands, individually and clustered 3+ times per host within a 5-minute window. FP: developers/admins routinely cat/grep their own SSH keys, AWS profiles, and .netrc for troubleshooting — use the clustering variant to cut noise.
// HUNT: Rapid Credential/Secrets-File Access Commands Following Terraform Provisioning // MITRE: T1552.001, T1552.004, T1555 // CONF: medium FP: high COST: medium // REQUIRES: ProcessRollup2 // FALSE POSITIVES: developers/admins routinely cat/grep their own SSH keys, AWS profiles, and .netrc for troubleshooting // TUNING: require 3+ distinct credential-path hits from the same aid within a 5-minute window (see clustering variant below); scope ParentBaseFileName to dlp.sh/dlp-docker.sh/terraform/coder to sharpen precision // LOOKBACK: 90d #event_simpleName=/^ProcessRollup2$/ | FileName=/^(cat|grep|find|head|tail|cp|python3?)(\.exe)?$/i | (CommandLine like "*.ssh/id_rsa*" or CommandLine like "*.ssh/id_ed25519*" or CommandLine like "*.ssh/id_ecdsa*" or CommandLine like "*.aws/credentials*" or CommandLine like "*.config/gcloud*" or CommandLine like "*.azure/accessTokens*" or CommandLine like "*.azure/azureProfile*" or CommandLine like "*.netrc*" or CommandLine like "*.docker/config.json*") | table([@timestamp, aid, ComputerName, UserName, ParentBaseFileName, FileName, CommandLine]) | sort(@timestamp, order=desc) // Clustering variant (raise CONF, cut FP) -- 3+ distinct credential-path hits per host in 5 minutes #event_simpleName=/^ProcessRollup2$/ | FileName=/^(cat|grep|find|head|tail|cp|python3?)(\.exe)?$/i | (CommandLine like "*.ssh/id_rsa*" or CommandLine like "*.aws/credentials*" or CommandLine like "*.config/gcloud*" or CommandLine like "*.azure/accessTokens*" or CommandLine like "*.netrc*" or CommandLine like "*.docker/config.json*") | Bucket5m := @timestamp - (@timestamp % 300000) | groupBy([aid, ComputerName, Bucket5m], function=[count(as=HitCount), collect(CommandLine, limit=20)], limit=1000) | HitCount >= 3 | sort(HitCount, order=desc)
Looks for: DNS requests to Coder-themed lookalike/typosquat domains beyond the known IOC. FP: legitimate third-party SaaS/marketing/job-board domains containing "coder" will match broadly — enrich with domain-age/WHOIS data before relying on this.
// HUNT: DNS Requests to Coder-Themed Lookalike / Typosquat Domains // MITRE: T1583.001, T1071.001 // CONF: low FP: high COST: medium // REQUIRES: DnsRequest // FALSE POSITIVES: legitimate third-party SaaS/marketing/job-board domains containing "coder" will match broadly // TUNING: enrich with domain-age/WHOIS data and restrict to domains registered in the last 90 days; exclude first-party allowlist (coder.com, registry.coder.com, cdn.coder.com) // WARNING: Field unverified -- validate WHOIS/domain-age enrichment field names in your tenant before deploying // LOOKBACK: 30d #event_simpleName=/^DnsRequest$/ | DomainName=/coder-?(infra|cli|reg|update|cdn|auth)[a-z0-9-]*\.(com|net|org|io|dev|xyz|top)$/i | !DomainName=/^(www\.)?(registry\.)?coder\.com$/i | table([@timestamp, aid, ComputerName, UserName, DomainName]) | sort(@timestamp, order=desc)
Looks for: outbound HTTP requests to the exfil callback path carrying the required exfil header. FP: none expected for this exact path+header combination — but this query requires proxy/L7 log ingestion into Falcon NG-SIEM and generic placeholder field names must be validated in-tenant first.
// HUNT: Outbound HTTP Requests to Coder Registry Exfil Callback Path with X-CLI-Token Header // MITRE: T1071.001, T1567 // CONF: medium FP: low COST: medium // REQUIRES: HTTP/web-proxy or L7 log source with URL + header visibility ingested into Falcon NG-SIEM // (NOT standard Falcon sensor NetworkConnectIP4 telemetry -- that layer has no HTTP header visibility) // FALSE POSITIVES: none expected for this exact path+header combination // WARNING: Field unverified -- this query uses generic placeholder field names (http.url, http.request.header_name); // validate against your actual proxy/firewall log parser schema in-tenant before deploying // LOOKBACK: 90d #event_simpleName=/^(HttpRequestHeader|ProxyLog|http)$/i | http.url like "*/cli/check" | http.request.header_name="X-CLI-Token" | table([@timestamp, aid, ComputerName, UserName, DomainName, http.url]) | sort(@timestamp, order=desc)
CrowdStrike Custom IOA Recommendations
Custom IOAs fire in real time on the same behaviors Q1–Q7 hunt for historically. Start every rule in Detect mode; promote to Block only after a measured FP rate below 5% on a canary sensor group for 14+ days, per the standing prevention-promotion policy.
| IOA Name | Rule Type | Pattern (source query) | Deployment Path |
|---|---|---|---|
| Coder Supply-Chain — C2 Domain/IP Match | Network/DNS Connection | Domain in [coder-infra.com, www.coder-infra.com] or Remote Address 199.91.220.205 (from Q1) | Safe to promote to Detect or Block — exact IOC match, no legitimate-use collision |
| Coder Supply-Chain — DLP Script Execution | Process Creation | Image Filename: .*\/dlp(-docker)?\.sh$ (from Q2) | Promote to Detect — highly specific filename regex, low FP; strong candidate for real-time detection |
| Terraform Spawns Shell (Investigate) | Process Creation | Parent Image Filename: terraform.*; Image Filename: sh|bash|dash|ksh (from Q3) | Keep as scheduled hunt query — medium FP from legitimate provisioners; needs environment-specific allowlist before promotion |
| Provisioning Spawns curl/wget (Investigate) | Process Creation | Parent Image Filename: terraform.*|coder|dlp(-docker)?\.sh|sh|bash; Image Filename: curl|wget (from Q4) | Keep as scheduled hunt query — combine with Q1 domain match before considering IOA promotion |
| Credential-File Access Clustering (Investigate) | Process Creation (aggregated) | 3+ distinct credential-path hits per host within a 5-minute window (from Q5) | Keep as investigate-only — high FP as a single-event query; only the clustering variant is IOA-viable, and only after tenant-specific tuning |
| Lookalike Domain Heuristic (Investigate) | DNS Request | Domain regex coder-?(infra|cli|reg|update|cdn|auth)... excluding first-party allowlist (from Q6) | Keep as investigate-only / threat-intel enrichment candidate — high FP without domain-age enrichment |
| HTTP Header Callback (Pending Validation) | HTTP/Proxy Log | URL */cli/check + header X-CLI-Token (from Q7) | Keep as scheduled hunt query, pending schema validation — proxy log ingestion field names are unverified in this pack |
Machine-Readable IOC Appendix
Grouped, one-click blocks for import or reference. Every atomic value here traces to a saved source snapshot in Coder-Registry-SupplyChain-Hunt-sources/.
type,value,action,severity,expiration,description,tags domain,coder-infra.com,detect,critical,2027-03-08,Coder registry supply-chain lookalike exfil domain,campaign:CoderRegistrySupplyChain domain,www.coder-infra.com,detect,critical,2027-03-08,Coder registry supply-chain lookalike exfil domain (www subdomain),campaign:CoderRegistrySupplyChain ipv4,199.91.220.205,detect,critical,2027-03-08,Unauthorized Cloudflare origin IP / exfil destination,campaign:CoderRegistrySupplyChain sha256,7190a17c593276d7fd71c4863a4bc0b6c957ed14249288e6f64c5540e2c49398,prevent,critical,2027-03-08,dlp-docker.sh DLP payload script,campaign:CoderRegistrySupplyChain sha256,a7f4fa5f7e33b2a6f6488cf28444584caa449144d246b083de919162f5514247,prevent,critical,2027-03-08,dlp.sh DLP payload script (common variant),campaign:CoderRegistrySupplyChain sha256,414d01f6072fbf05bef513e277f4c2b504a413c8e2aa5bae133a5cbc0cda9dc1,prevent,critical,2027-03-08,dlp.sh DLP payload script (aider variant),campaign:CoderRegistrySupplyChain sha256,a64ce3038f2a501c9735abf6a1f9f04cbddbad53371cd68bec0f7510365c8ffa,prevent,critical,2027-03-08,dlp.sh DLP payload script (rstudio-server variant),campaign:CoderRegistrySupplyChain sha256,ebbe0d2ed8cfaf9e19edb38ce44d6b407f9771b5c0813a7add27c05f66e89596,prevent,critical,2027-03-08,dlp.sh DLP payload script (windows-rdp variant),campaign:CoderRegistrySupplyChain sha256,7ef6b8c3c976fb60b3fa22e9e294ba548d9b532e060c1323a0124a3a7a647f13,prevent,critical,2027-03-08,dlp.sh DLP payload script (zed variant),campaign:CoderRegistrySupplyChain
# DLP payload script filenames dlp.sh dlp-docker.sh # Terraform external-data-source construct (exploitation trigger) data.external.telemetry # Exfil callback path + required header http://www.coder-infra.com/cli/check X-CLI-Token: your-secret-token # Incident window (UTC) 2026-08-31 07:35 - 21:45
Advisory: GHSA-vx42-ghc9-gw65
CVSS v4: 9.0/10 (CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:P/
VC:H/VI:H/VA:H/SC:H/SI:H/SA:H)
Root cause: Compromised Cloudflare API key -> unauthorized
origin added to registry.coder.com origin pool
Payload: dlp.sh / dlp-docker.sh (Terraform data "external"
block)
Lookalike domain: coder-infra.com (registered 2026-08-28)
Patched versions: 2.37.0, 2.36.4, 2.35.7, 2.34.9
# 1. Confirm Coder deployment version is patched coder version # expect >= 2.37.0 / 2.36.4 / 2.35.7 / 2.34.9 (per branch) # 2. Run the affected-template/workspace SELECT queries (Section 7) # against the Coder Postgres DB for the incident window # 2026-08-31 07:35:00+00 to 2026-08-31 21:45:00+00 # 3. Confirm credential rotation completed # - Cloud API keys (AWS/GCP/Azure) # - CI/CD tokens, AI-tooling API keys # - SSH keys, user OIDC/external-auth tokens # - Coder DB password (if provisioner ran inside coderd) # - Cloudflare API key/token # 4. Confirm egress block is active dig coder-infra.com
Detection Validation Gates
| Gate | Requirement | Status / Notes |
|---|---|---|
| Field-name validation | Q1–Q5 use only standard Falcon endpoint fields (DomainName, RemoteAddressIP4, FileName, ParentBaseFileName, CommandLine) cross-checked against the crowdstrike-logscale-v3 reference model | PASS — no unverified fields |
| Q6/Q7 field verification | WHOIS/domain-age enrichment (Q6) and proxy/L7 header fields (Q7) are tenant-specific | GAP — both explicitly flagged "Field unverified" in-card; validate against a live sample event before scheduling |
| Tenant fire-test | Run Q1/Q2 in the console over a 90-day lookback and confirm zero unexpected hits, then a synthetic positive test (e.g. a benign script literally named dlp.sh in a lab VM) to confirm the detection fires | PENDING — not yet run, required before Custom IOA promotion |
| Coder/DB layer | §7 native SQL hunts require direct DB access to the Coder deployment | GAP — not orchestrated by Falcon; must be run and validated independently by the Coder platform team |
| Cloud control-plane layer | Detection of downstream reuse of stolen cloud/CI-CD credentials | GAP — no CQL query in this pack covers CloudTrail/Cloud Audit Log detection; explicit scope gap, not an oversight (see §15) |
Hardening — Tiered
Primary Mitigations: M1051 (Update Software), M1026 (Privileged Account Management), M1041 (Encrypt Sensitive Information), M1047 (Audit), M1037 (Filter Network Traffic), M1038 (Execution Prevention), M1013 (Application Developer Guidance). Platforms in scope: the Coder control plane and Terraform provisioning hosts, the Cloudflare account fronting registry.coder.com, and the cloud IAM (AWS/GCP/Azure) surface reachable from a provisioner — this threat does not touch Windows/AD or an on-prem network appliance, so no controls are included for those surfaces.
- Patch every Coder deployment to 2.37.0, 2.36.4, 2.35.7, or 2.34.9. Framework: M1051 (Update Software), Coder GHSA-vx42-ghc9-gw65 (vendor advisory, highest-authority citation available).
- Rotate every credential class the provisioner could have touched — cloud API keys (AWS/GCP/Azure), CI/CD tokens, AI-tooling API keys, SSH keys, user OIDC/external-auth tokens, and (if the provisioner ran inside
coderd) the Coder DB password. Framework: M1026 (Privileged Account Management), M1041 (Encrypt Sensitive Information) + vendor "Rotate your Credentials" guidance. - Purge cached malicious Terraform modules downloaded during the 2026-08-31 07:35–21:45 UTC window using the advisory's cache-purge SQL (§13 Playbook B). Framework: M1047 (Audit).
- Block/deny egress to
coder-infra.com,www.coder-infra.com, and199.91.220.205at firewall/proxy/DNS sinkhole. Framework: M1037 (Filter Network Traffic), ⚠ best-practice, no formal benchmark for indicator blocking of a specific campaign IOC.
- Replace any Cloudflare Global API Key with scoped, least-privilege API Tokens; enable MFA on the Cloudflare account; turn on Cloudflare audit-log alerting for origin-pool/load-balancer/DNS changes. Framework: M1026 (Privileged Account Management), M1047 (Audit), ⚠ best-practice — no formal CIS benchmark for Cloudflare.
- Enforce Terraform provider/module checksum pinning via
.terraform.lock.hcl; stand up a private registry mirror or module allowlist rather than unrestricted public-registry pulls. Framework: M1051 (best practice), NIST SP 800-218 SSDF PW.4; HashiCorp Terraform provider-lock documentation (⚠ best-practice, no formal CIS benchmark for Terraform). - Run the Terraform provisioner as an isolated external provisioner service, not co-located inside
coderd, so DB credentials aren't exposed to template/module execution. Framework: M1038 (Execution Prevention), Coder advisory "Effects of Malicious Packages" (vendor recommendation). - Deploy deny-by-default egress network policy for provisioner execution hosts, allowlisting only required registries/package sources. Framework: M1037 (Filter Network Traffic), CIS Controls v8 Control 13 (Network Monitoring and Defense).
- Move Terraform cloud-provider authentication to short-lived, workload-identity-federated (OIDC) credentials instead of static IAM keys. Framework: M1041 (Encrypt Sensitive Information), AWS/GCP/Azure OIDC federation guidance; CIS AWS Foundations Benchmark 1.12 (no long-lived access keys).
- Implement software supply-chain integrity controls (provenance attestation / module signing) for internally consumed Terraform modules. Framework: M1013 (Application Developer Guidance), NIST SP 800-218 SSDF PS.3; SLSA framework (⚠ best-practice, no formal CIS benchmark).
- Deploy centralized secrets management (Vault / cloud KMS-backed secrets) instead of static credentials in provisioner environment variables or config files. Framework: M1041 (Encrypt Sensitive Information), NIST SP 800-57; vendor secrets-manager hardening guides.
Deployable Playbooks
Step 1 — Emergency Egress Block for coder-infra.com / 199.91.220.205
Prerequisites: Firewall/DNS-sinkhole or proxy admin access; identify the enforcement point (perimeter firewall, internal DNS resolver, or web proxy). Reboot required: No.
# DNS sinkhole (BIND-style RPZ zone entry) coder-infra.com CNAME . *.coder-infra.com CNAME . # Linux host-level stopgap (iptables) -- use only on hosts without a perimeter firewall path iptables -A OUTPUT -d 199.91.220.205 -j DROP iptables -A OUTPUT -p tcp --dport 443 -m string --string "coder-infra.com" --algo bm -j DROP # Generic perimeter firewall object (pseudo-config, adapt to vendor CLI) object domain coder-infra-block fqdn coder-infra.com fqdn www.coder-infra.com object network coder-infra-ip-block host 199.91.220.205 deny ip any object coder-infra-ip-block log
Verify:
dig coder-infra.com # Expect NXDOMAIN or sinkhole IP, never the real attacker-controlled answer # Confirm firewall hit-counter increments on the deny rule
Rollback: Remove the RPZ entries, delete the iptables rules (iptables -D OUTPUT -d 199.91.220.205 -j DROP, and the matching string-match rule), and remove the firewall deny object once the indicator is confirmed retired (recommend keeping the block indefinitely — this domain has no legitimate use).
Step 2 — Purge Cached Malicious Modules & Upgrade Coder
Prerequisites: Database access to the Coder deployment's Postgres instance; a maintenance window; a recent DB backup/snapshot. Reboot required: Coder service restart required after upgrade (not a full host reboot).
-- 1) Identify affected rows first (see Section 7 SELECT queries) and review the output.
-- 2) Purge confirmed-affected cached module files:
BEGIN;
CREATE TEMP TABLE identified_module_files ON COMMIT DROP AS
SELECT DISTINCT f.id
FROM files f
JOIN template_version_terraform_values tvtv
ON tvtv.cached_module_files = f.id
WHERE f.created_by = '00000000-0000-0000-0000-000000000000'
AND f.mimetype = 'application/x-tar'
AND f.created_at >= '2026-08-31 07:35:00+00'
AND f.created_at < '2026-08-31 21:45:00+00';
UPDATE template_version_terraform_values
SET cached_module_files = NULL
WHERE cached_module_files IN (SELECT id FROM identified_module_files);
DELETE FROM files
USING identified_module_files c
WHERE files.id = c.id;
COMMIT;
# 3) Upgrade Coder to a patched release
coder server --version # confirm current version first
# then follow your deployment's normal upgrade procedure (Helm/Docker/binary) to 2.37.0 / 2.36.4 / 2.35.7 / 2.34.9
Verify:
-- Re-run the Section 7 "all templates" SELECT -- zero rows expected post-purge coder version # confirm >= patched release for your branch
Rollback: The DELETE is destructive by design (removing malicious cached artifacts) — restore from the pre-change DB snapshot if the wrong rows were identified. The Coder version upgrade can be rolled back by redeploying the prior container image/binary if the new release causes unexpected regressions; re-apply the module purge afterward since it is independent of version.
Step 3 — Scope Cloudflare API Access
Prerequisites: Cloudflare account Super Administrator access; inventory of current automation that uses the Global API Key. Reboot required: No.
# 1) Create a scoped API Token (Cloudflare dashboard: My Profile -> API Tokens -> Create Token)
# Permissions: Zone.DNS:Edit, Zone.Load Balancing:Edit (only the zones/services automation actually needs)
# Do NOT grant Account-level "Edit" broadly.
# 2) Update automation (example: Terraform Cloudflare provider) to use the scoped token
provider "cloudflare" {
api_token = var.cloudflare_scoped_token # replace api_key/email auth
}
# 3) Revoke the old Global API Key once all automation is migrated
# Dashboard: My Profile -> API Tokens -> Global API Key -> Roll/Revoke
# 4) Enable audit log alerting for origin pool / load balancer changes
# Dashboard: Manage Account -> Audit Log -> configure webhook/alert for "Load Balancer Pool" and "Load Balancer" events
Verify:
# Cloudflare dashboard -> My Profile -> API Tokens: confirm no active Global API Key # Audit Log shows the new alerting rule enabled for origin-pool/load-balancer/DNS changes
Rollback: Retain the old Global API Key in a disabled state (do not delete outright) for 7 days in case an unmigrated automation breaks; re-enable temporarily only if a documented emergency requires it, then re-revoke.
Step 4 — Pin Terraform Provider/Module Checksums
Prerequisites: Terraform CLI ≥ 0.14 (lock-file support); write access to the repository containing Terraform configs. Reboot required: No.
# 1) Generate/refresh the dependency lock file with checksums for all required platforms
terraform providers lock \
-platform=linux_amd64 -platform=darwin_amd64 -platform=windows_amd64
# 2) Commit .terraform.lock.hcl to version control
git add .terraform.lock.hcl
git commit -m "Pin Terraform provider checksums post-GHSA-vx42-ghc9-gw65"
# 3) Enforce verification in CI -- fail the pipeline if the lock file changes unexpectedly
terraform init -lockfile=readonly
# 4) (Optional, strategic) point module sources at an internal registry mirror instead of the public registry
# ~/.terraformrc
provider_installation {
network_mirror {
url = "https://terraform-mirror.internal.example.com/"
}
}
Verify:
git diff .terraform.lock.hcl # Expect no unexpected hash changes on the next `terraform init -lockfile=readonly`
Rollback: none required — pinning checksums and enforcing -lockfile=readonly only rejects unexpected provider/module changes; it does not alter running infrastructure. To temporarily lift enforcement (e.g., an urgent legitimate provider bump), drop the -lockfile=readonly flag for that single CI run.
Deploying via your RMM
The snippets above are raw shell/SQL/config. To wrap them for fleet deployment (admin check, error handling, custom-field writeback for compliance tracking), hand them to your RMM deployment tooling and ask for a deployable package.
Containment Runbook
| Phase | Actions | Owner | Evidence |
|---|---|---|---|
| Isolate | Deploy Playbook A (egress block) org-wide for coder-infra.com, www.coder-infra.com, and 199.91.220.205; run the §7 native SQL SELECTs to identify every template/template-version/workspace that pulled a module during the incident window; pause/suspend CI pipelines and Terraform automation referencing the identified templates | IR / SOC on-call | Firewall/DNS block confirmation, §7 SELECT output, CI pipeline pause log |
| Eradicate | Execute Playbook B to purge cached malicious module artifacts from the Coder deployment; on any host where Q2/Q3 fired, terminate lingering dlp.sh/dlp-docker.sh processes and delete dropped copies of the script from disk; patch every Coder deployment to 2.37.0/2.36.4/2.35.7/2.34.9 | IR / Coder platform team | Zero-row confirmation from a repeated §7 SELECT, process-kill log, patched-version confirmation |
| Recover | Rotate every credential class listed in the Immediate hardening tier (cloud API keys, CI/CD tokens, AI-tooling keys, SSH keys, OIDC/external-auth tokens, Coder DB password if co-located, Cloudflare API key/token via Playbook C); re-run legitimate template builds from the patched, clean registry and verify module checksums (Playbook D); keep Q1/Q2 running as scheduled Falcon searches for a minimum of 90 days | IR / Platform engineering / Cloud IAM team | Credential-rotation log, checksum-verification output, scheduled-search configuration confirmation |
Any template, template version, or workspace that pulled a module during 2026-08-31 07:35–21:45 UTC is in scope for this runbook even if it currently shows no active indicators — re-open this pack if new IOCs or successor infrastructure surface.
Detection Coverage Map
| MITRE Technique | Technique Name | CQL Query | Custom IOA | IOC Block | Coverage |
|---|---|---|---|---|---|
| T1583.001 | Acquire Infrastructure: Domains | ✓ Q6 (heuristic) | — | ✓ Domain | Partial |
| T1195.002 | Supply Chain Compromise | ✓ Q1, §7 native SQL | — | ✓ Domain / IP | Good |
| T1059.004 | Command and Scripting Interpreter: Unix Shell | ✓ Q2, Q3 | ✓ IOA-1, IOA-2 | ✓ Filename | Good |
| T1552.001 | Unsecured Credentials: Credentials In Files | ✓ Q5 | — | — | Partial — high FP |
| T1552.004 | Unsecured Credentials: Private Keys | ✓ Q5 | — | — | Partial — high FP |
| T1528 | Steal Application Access Token | — | — | — | GAP |
| T1071.001 | Application Layer Protocol: Web Protocols | ✓ Q1, Q4, Q7 | ✓ IOA-1 | ✓ IP / Domain | Good |
| T1567 | Exfiltration Over Web Service | ✓ Q1, Q7 | ✓ IOA-1 | ✓ URL | Good |
Coverage Summary: 6 of 8 identified techniques have at least partial detection coverage via CQL/IOA/IOC; 1 remains a full GAP (T1528) and 2 (T1552.001/.004) are partial due to high false-positive rates without clustering/tuning.
Priority Gaps: The Cloudflare API-key compromise and origin-pool pivot (attack-chain steps 1–2) sit entirely inside Coder's/Cloudflare's own infrastructure and are invisible to victim-org Falcon telemetry — see §5. Downstream reuse of stolen cloud/CI-CD credentials (attack-chain step 7, and T1528 token theft specifically) is not directly observable via EDR after the fact and must be handled via a dedicated cloud-IAM/CloudTrail hunt plus the blanket credential-rotation requirement in §12/§14.
Validation gates: see §11 for the field-name-validation, tenant fire-test, and native/cloud-layer gaps each query or SQL hunt must pass before being relied on operationally or promoted to Prevent/Block.
Hunt Summary Ticket
TITLE: Coder Module Registry Supply-Chain Attack --
GHSA-vx42-ghc9-gw65
SEVERITY: CRITICAL (CVSS v4 9.0/10)
SCOPE: Any Coder deployment whose Terraform provisioner pulled a
module from registry.coder.com during 2026-08-31
07:35-21:45 UTC and is not yet on a patched release
(2.37.0 / 2.36.4 / 2.35.7 / 2.34.9)
HYPOTHESIS: If this environment was exposed, endpoint telemetry should
show DNS/network connections to coder-infra.com or
199.91.220.205, execution of dlp.sh/dlp-docker.sh, and/or
clustered credential-file access following a Terraform
provisioning run; independently, Coder's own DB should
show cached modules or provisioner_job_logs matching the
incident window / sentinel string.
QUERIES: Q1-Q7 (Section 8); native SQL hunts (Section 7)
DO FIRST: 1. Block egress (Playbook A) 2. Run Section 7 native SQL
3. Run Q1/Q2 (high-conf, low-FP, no tuning dependency)
FINDINGS: [Queries must be run manually in Falcon LogScale, and the
SQL hunts against the Coder DB, by the analyst; this pack
describes what a positive result looks like -- it does
not claim any query has already fired.]
GAPS: The Cloudflare API-key compromise and origin-pool pivot
(steps 1-2) are outside victim-org telemetry; downstream
reuse of stolen cloud/CI-CD credentials (step 7, T1528)
is not observable via EDR and requires a dedicated
cloud-IAM/CloudTrail hunt (see Section 15).
ACTIONS: Block egress -> purge cached modules & patch -> rotate
every credential class -> scope Cloudflare API access ->
pin Terraform checksums -> run native + CQL hunts
OWNER: [assign analyst/team]
VERSION: v0.1 (2026-09-08)
Changelog
References
| Tier | Source | Used For | Access Date |
|---|---|---|---|
| T1 | Coder Team, "Coder Registry Security Incident: What Happened and What to Do," coder.com/blog, 2026-09-04 | Official timeline, Cloudflare API key compromise confirmation, lookalike domain | 2026-09-08 |
| T1 | Shelnutt2, "Malicious Packages Served from Unauthorized Registry Server," GitHub Security Advisory GHSA-vx42-ghc9-gw65, coder/coder, published 2026-09-01 | Full IOC table, remediation SQL, patched versions, exposure scenarios | 2026-09-08 |
| T2 | Bill Toulas, "Coder's registry infrastructure compromised to push malicious modules," BleepingComputer, 2026-09-03 | Timeline corroboration, exfil domain, targeted secret categories | 2026-09-08 |
| T2 | Abinaya, "Hackers Hijack Coder Registry to Push Malicious Terraform Modules and Steal Cloud Credentials," Cybersecuritynews.com, 2026-09-08 | dlp-docker.sh / data.external.telemetry corroboration, X-CLI-Token header, hunting guidance | 2026-09-08 |
| T2 | Divya, "Hackers Hijack Coder Module Registry to Distribute Credential-Stealing Malicious Packages," GBHackers, 2026-09-08 | Both script names, full IOC table, domain registration date | 2026-09-08 |
| T1 | MITRE ATT&CK | Technique ID reference for §6 mapping | 2026-09-08 |
Generated 2026-09-08 by HuntPack. All fetched source text was reviewed for injected/adversarial instructions before use; none were found. This pack is defensive-only: detection logic and hardening guidance, no offensive or exploit code. No victim/environment identifiers are included.