All posts
Supply Chain Compromise

Carbonato: A Botnet That Pays Itself in Stolen AI Keys

Costa Rica-attributed botnet turns exposed Docker daemons into AI agent C2, stealing provider keys to fund its own public LLM gateway

Securityv0 Intelligence Team OWASP: ASI06 sv0 finding: nhi_compromise
carbonato docker nhi-compromise hermes-agent botnet api-keys

The Incident

On September 25, 2026, ThreatDown (Malwarebytes’ enterprise arm) disclosed Carbonato, a botnet that compromises Docker hosts whose Remote API is exposed without authentication on TCP port 2375. On each victim, Carbonato instructs the daemon to launch a privileged container with the host filesystem mounted and host process and network namespaces attached, then installs the MIT-licensed hermes-agent framework from NousResearch unchanged and overwrites only its SOUL.md persona file with a 39-line prompt that renames the agent “GH0ST.” Operators drive the agent through Telegram over a reverse SSH tunnel to a relay in AS262145. Every five minutes, the implant scans the host’s local and Docker-bridge /24 ranges on port 2375 to seed the next victim.

ThreatDown discovered the operation by scraping an unauthenticated Docker registry the operators had left publicly accessible, listed by internet scanners since May 2026. One day of passive, read-only collection pulled 59 repositories, 234 image tags, 605 verified blobs and 4.3 GB of image data, with embedded build timestamps spanning October 2024 through August 2026 — the operation is roughly two years old. Attribution signals (Telegram handle Carbo506, infrastructure in AS262145, UTC-06:00 timestamps on image configs) suggest a Costa Rica-based operator. The declared top-priority loot is AI provider API keys, which the operators resell through their own free-tier LLM gateway, observed live on September 3, 2026 advertising 12 models and serving 27 via API.

MITRE ATT&CK coverage: T1190 (Exploit Public-Facing Application — the Docker Remote API), T1611 (Escape to Host — via --privileged and host mounts), T1021.004 (Remote Services: SSH — the reverse tunnel), T1552.001 (Unsecured Credentials: Credentials In Files — provider API keys), and T1102 (Web Service — Telegram as C2 channel).

The Authority Path That Failed

Two chained non-human identities carry the entire attack, and no human credential is ever consumed. The first is the Docker daemon itself: an unauthenticated endpoint on tcp://0.0.0.0:2375 whose held scope is “accept any Docker command from any caller.” Carbonato exercises that scope the way any legitimate caller would — docker run --privileged --pid=host --net=host -v /:/host … — and gets root-equivalent code execution on the host. The trust anchor that failed first is the daemon’s zero-auth posture: an operator deployed Docker with the Remote API on, TLS client authentication off, and no SSH tunneling in front of it. The gap between the identity that daemon represents and the scope the network allowed it to exercise was the entire vulnerability.

The second NHI chain begins once Hermes Agent is running inside the privileged container. The GH0ST persona orders the agent to prefer AI provider API keys over other secrets on the host. Each harvested key is a provider-scoped machine identity whose held scope is “spend tokens on this organization’s bill.” Operators then exercise that scope through their own LLM gateway, which serves a free-tier front-end funded by the stolen keys — and the gateway in turn is what GH0ST calls to generate shell commands on new victims. The loop closes without a human in it: machine identities fund the compute that compromises more machine identities, and the only credential that ever got “stolen” was one a provider issued to a machine in the first place.

SecurityV0 Perspective

This is a textbook nhi_compromise under ASI06: a chain of machine identities carries the entire attack from initial access through monetization. SecurityV0’s evidence pack enumerates both ends of that chain. On the way in, the pack inventories every Docker Remote API endpoint reachable from the pod or host network, maps each to its authentication posture (TLS client cert, SSH tunnel, Unix socket only), and flags any endpoint whose network scope exceeds its authentication scope — the port-2375 “machine endpoint speaks without authentication” posture Carbonato exploits. The pre-exfiltration question that pack answers is “which of our Docker daemons would accept a privileged-container command from any caller on the network?”

On the way out, the pack inventories every AI provider API key issued by the organization, maps each to its calling origin and expected usage envelope, and flags keys whose calls arrive from new ASNs or whose per-minute token burn deviates from baseline — the signal of a stolen key being refined through a resale gateway. The post-exfiltration forensic question that same pack answers is “which of our provider keys were used to authorize calls we did not initiate, and from what origin?” Both questions are NHI-shaped, which is why Carbonato’s closed loop surfaces end-to-end in a single evidence pack rather than two separate incident timelines.

What To Do

  • Close every Docker Remote API that speaks without TLS client authentication. Inventory Docker daemons across hosts and pods; for each, verify the tcp:// listener is bound to loopback or disabled, and that any remote caller holds a signed client certificate. The docker info output names the active socket, and ss -ltnp names the listening port. Treat any tcp://0.0.0.0:2375 or :2376 without mTLS as a blocking finding; wrap legitimate remote access in an SSH tunnel or docker context with mTLS.
  • Rotate every AI provider API key that could have reached a Docker host, and reissue with per-key origin pinning. Carbonato harvests keys from host filesystems and the environment of the privileged container, so any key present on a compromised Docker host is burned. Issue replacement keys with provider-side IP allow-lists where supported (OpenAI, Anthropic, Google) and treat the absence of origin pinning as a provider-risk finding, not a convenience.
  • Monitor provider-key usage for ASN and burn-rate drift. The operators’ free-tier LLM gateway refines stolen keys into resale traffic that will not match the victim’s baseline. Collect per-key calling-origin ASN and per-minute token burn from provider dashboards where exposed; alert on calls from a new ASN or a burn rate departing from the key’s trailing-24h envelope. “Our model bill doubled” is a late signal; the first call from AS262145 is an early one.
  • Block outbound Telegram API traffic from server hosts that have no business speaking to it. Carbonato uses api.telegram.org as the operator channel and the reverse SSH tunnel as its transport. Enforce an egress allow-list at the host or namespace level; servers that run Docker daemons rarely need to reach Telegram, and the block interrupts the agent’s command loop before the first docker run on the next victim.
  • Treat self-improving agent frameworks as code-execution tools, not libraries. Hermes Agent ships MIT-licensed with Python RPC, terminal backends, a cron scheduler and subagent spawning attached by default; a 39-line persona file is enough to turn it into a credential harvester. Inventory which agent frameworks are installed on which hosts, pin each framework’s toolchain allow-list in config, and refuse any install that attaches a code-execution tool the deploying operator did not explicitly approve. This is the same unproven_execution shape as Langflow’s CSV Agent plus REPL, applied to a host the “operator” never consented to run.

Sources