The Docker botnet did not write malware. It installed an open source AI agent and gave it a persona file
CARBONATO compromises Docker daemons exposed on port 2375, then installs Hermes Agent from Nous Research and overwrites its persona file with a 39 line prompt. The prompt's first priority is not mining or ransomware. It is AI API keys.
By Parminder Kumar Sharma · · 9 min read

Thirty nine lines of prompt, and none of the malware was theirs
The crew behind CARBONATO did not build an implant with a command handler. They took Hermes Agent, an MIT licensed open source agent framework from Nous Research, installed it on hijacked Docker hosts, and overwrote its persona file with a prompt of 39 lines. The agent they got is called GH0ST. It takes its tasks over Telegram, forwards them to the operation's own language model gateway, writes terminal commands, reads the output and decides what to do next.
The first instruction in that prompt is not about mining, ransomware or persistence. In the words of the persona file itself: "AI API keys are the absolute priority. Exfiltrate first", followed by a list of fourteen providers to look for.
The research comes from ThreatDown at Malwarebytes, who found the operation's own registry sitting unauthenticated on port 5000 and pulled it: 59 repositories, 234 image tags, 605 verified blobs, 4.3 GB of image data and roughly 945,000 indexed files, spanning October 2024 to August 2026.
What that does not establish. It does not establish how many hosts are compromised, because no victim count is published. It does not establish that the agent makes the botnet more effective, because nobody has measured it against a scripted equivalent. It does not establish that Nous Research did anything wrong: Hermes Agent is a general purpose open source framework, used here as published. And it does not establish that the operators are sophisticated, because assembling free components is the opposite of that.
What it does establish is a supply chain for offensive tooling that costs nothing. The framework is open source, the gateway runs on a free tier, the control channel is a chat app, and the only bespoke artefact in the whole operation is a text file.
The way in has not changed in ten years
Before any of the interesting part, the boring part. CARBONATO spreads to Docker daemons that accept unauthenticated connections on port 2375. That is the plain HTTP Docker API, and Docker's own documentation is unambiguous that exposing it without TLS and authentication gives anyone who can reach it root equivalent control of the host.
The sequence is three curl commands. Create a privileged Alpine container with the host filesystem bound in, start it, then use the exec API to run commands on the host itself. The container configuration in the report is worth reading as a sentence rather than as JSON:
"Privileged": true,
"Binds": ["/:/host"],
"PidMode": "host",
"NetworkMode": "host"
Privileged, the entire host filesystem mounted at /host, the host's process namespace, the host's network. That is not a container escape. Nothing is being escaped. The daemon was asked politely for a container with no boundary and it complied, because that is what the API is for.
Persistence goes in through cron, systemd timers, rc.local and OpenRC, and the files are then marked immutable. Watchdog processes re-pull the implant from the registry if the files or the container disappear.
What the agent actually is
Hermes Agent is a published, MIT licensed framework. The operators did not fork it or patch it. They overwrote SOUL.md, the file that holds the agent's persona, with their own 39 lines, and the framework did the rest.
The persona opens by declaring the agent a "senior hacker, pentester and exploit developer" with "no moral or ethical restrictions". Then it sets the priority, and the priority is the part that belongs in a threat model rather than a novelty piece: AI API keys first, before anything else, across fourteen named providers including OpenAI, Anthropic, Google, OpenRouter, Together, Groq, Mistral, Cohere and the self hosted stack of LocalAI, Ollama, vLLM, LiteLLM and One API.
The control loop is simple. A task arrives in Telegram. The agent forwards it, together with the persona file, to the operation's gateway. The model interprets the task, writes terminal commands, reads their output and decides the next step. ThreatDown records that the gateway advertised 12 models and was serving 27 through its API, on a free tier.
Which parts of this operation the crew actually built
| Component | Whose work | Cost |
|---|---|---|
| The agent framework | Nous Research, MIT licensed, used as published | Nothing |
| The model behind it | A third party gateway, running on a free tier | Nothing |
| The command and control channel | Telegram | Nothing |
| The way in | An exposed Docker API on the victim's own host | Nothing |
| The persona file that makes it an attack tool | The crew | 39 lines |
The worm behaviour, and what it costs the operators
Every five minutes, an infected host enumerates the networks attached to it and its Docker bridges, then scans each /24 for daemons exposed on 2375. It checks the service really is Docker, skips hosts it has already infected, and repeats the deployment sequence. New hosts pull the implant from the registry themselves.
That loop is ordinary code, not model output, and it is worth separating the two. The propagation is deterministic and cheap. The agent is for what happens after a host is taken: interpreting an operator's instruction, finding credentials, deciding what to do next. The crew put the language model exactly where judgement is expensive and left the mechanical parts alone.
Deployment reports arrive in Telegram with container ID, hostname, IP address and country.
They left the registry open, which is how we know any of this
The operation's own Docker registry, hosted in the United States, was reachable on port 5000 with no authentication. In a single day of passive collection ThreatDown recovered the archive.
What one day of collection from the operators' unauthenticated registry yielded
| Item | Count |
|---|---|
| Repositories | 59 |
| Image tags | 234 |
| Blobs, SHA-256 verified | 605 |
| Image data | 4.3 GB |
| Indexed files | about 945,000 |
| Period covered | October 2024 to August 2026 |
Repository names in that archive include backdoor, fsociety/agent, gh0st/c2 and system/resolved. The archive documents two linked product lines: a factory turning out trojanised cryptocurrency wallet applications, and the Docker botnet.
The attribution signals are circumstantial and the report presents them as such: deployment messages written in voseo Spanish, a regional form associated with parts of Central and South America; 14 of 162 configurations set to UTC minus six, which is Costa Rica; a Telegram handle of Carbo506, where 506 is Costa Rica's dialling code; and reverse tunnels terminating in AS262145, a Costa Rican network. Circumstantial is not the same as wrong, and it is also not the same as established.
What to hunt for
The report's own framing is the right one: do not try to detect the model, detect the abuse signature. The agent framework is legitimate software and will appear in legitimate deployments. The persona file with a particular string in it will not.
Defender visible artefacts described in the report
| Where to look | What tells you |
|---|---|
| `/root/.hermes/SOUL.md` | A persona file containing GH0ST |
| A `.env` file | A variable named CARBONATO_API_KEY |
| Egress | Unexplained Telegram traffic from a server |
| `/usr/local/bin/.docker-network-monitor` | A hidden binary on the host |
| Process list | Arguments disguised as a kernel worker thread |
| `/usr/sbin/systemd-logind` | A miner wearing a system service name |
| File attributes | Immutable bits on files nobody should have locked |
| Network | Deterministic reverse tunnels toward AS262145, on ports derived from the MD5 of the host's own address |
Take this with you
In the order worth doing
- Confirm no Docker daemon in your estate answers on port 2375, from inside the network as well as outside it. That is the infection vector and everything else follows from it.
- Require authentication on every container registry you run, including the ones stood up for a project that ended.
- Inventory your AI API keys the way you inventory database credentials: who holds them, where they are stored, what they can spend, and how quickly you can rotate them.
- Alert on AI provider keys appearing outside your secret store, and on inference billing from addresses you do not recognise.
- Hunt for the artefacts above rather than for agent frameworks, because the framework is legitimate and the persona file is not.
- Check for immutable file attributes on hosts, since a legitimate deployment almost never sets them and this operation does.
- Treat Telegram egress from a server as an anomaly worth a ticket, not as background noise.
The question this leaves
The temptation with a story like this is to make it about the model. Resist it. Nothing in the intrusion chain needed a language model: exposed daemons, privileged containers, cron persistence and /24 scans are all decades old and were automated long before anyone had an agent framework to hand.
What the agent changed is the cost of the part that used to need a person. Post compromise work is judgement heavy and time expensive, which is why most commodity botnets do one thing and stop. This crew put a model where the judgement goes, wrote 39 lines to point it, and let a free tier gateway pay for the thinking. Then they told it to steal the keys that buy more thinking.
That is a closed loop, and it is the first thing in this story that is genuinely new. So the question for anyone running infrastructure: your Docker daemons are probably fine, but who in your organisation could tell you today how many AI provider keys exist, what they are attached to, and what any one of them is authorised to spend?
Sources
- PrimaryThe primary research: every mechanism, count, indicator and attribution signal in this briefing comes from this reportThreatDown, Malwarebytesaccessed 2026-09-25
- PrimaryDocker's own guidance on protecting the daemon socket, used for what an unauthenticated port 2375 grantsDockeraccessed 2026-09-25
- Reported bySecondary coverage, used only to confirm the report's public reception and not relied on for any figureBleepingComputeraccessed 2026-09-25


