CSIPE

Published

- 21 min read

The AI Botnet Started With an Open Docker Door


Books by the author

Compare all 5

As an Amazon Associate I earn from qualifying purchases. Buying through these links costs you nothing extra and helps pay for the blog.

A Docker service was listening on the network with no authentication. That single fact gave a remote caller the power to start a privileged container, mount the host filesystem, and run commands on the underlying server. The AI agent arrived later.

ThreatDown published its Carbonato investigation on 22 September 2026 after finding an unauthenticated container registry and collecting one day of read-only evidence from it. The recovered archive covered activity from October 2024 through August 2026. Among the images and configuration records, researchers found a botnet aimed at Docker daemons exposed on port 2375, plus an unrelated-looking but linked operation distributing counterfeit cryptocurrency wallet applications (ThreatDown).

Carbonato is newsworthy because the operators installed Hermes Agent, an open-source agent framework, on compromised hosts and controlled it through Telegram. That detail deserves attention. It also creates a temptation to tell the story backwards, as if a clever model somehow invented its way through Docker. It did not. Scripts found the open service, created the privileged container, established persistence, and spread to nearby hosts. The model entered an interactive command loop only after those steps had already delivered control.

For developers and platform teams, the useful lesson is not “block AI agents.” It is much less fashionable and much more actionable: treat every infrastructure control plane as root-equivalent authority, keep it off untrusted networks, and assume a host is compromised if an unauthenticated caller could use it. The strange new component does not cancel the old boundary failure.

What ThreatDown found, and what remains a claim

ThreatDown says its researchers found a US-hosted Docker Registry on port 5000 in August 2026. Two unauthenticated read requests showed that the registry exposed its catalogue. During one day of passive collection, the team recovered 59 repositories, 234 image tags, 605 verified blobs, and 4.3 GB of data. Configuration records included image histories, entry points, environment variables, command history, control addresses, bot tokens, and a shared password (ThreatDown).

Those records provided a unusually detailed view of the operation. Image timestamps ran from October 2024 to August 2026. ThreatDown separated the material into two linked product lines: trojanized cryptocurrency wallet applications and the Carbonato botnet. On 3 September 2026, the researchers said six of seven known registries, the phishing sites, the content delivery service, and the operation’s language-model gateway were still online.

The botnet’s entry path was an unauthenticated Docker daemon, generally exposed on TCP port 2375. Once it found one, Carbonato asked that daemon to start a privileged container with access to the host filesystem, process namespace, and network namespace. From there, the implant could act on the host itself. ThreatDown found persistence through several Linux startup mechanisms, a reverse connection for later access, and watchdogs designed to restore the implant if files or containers disappeared.

The agent came after the foothold and persistence. ThreatDown reported that the implant installed the standard Hermes Agent framework, then replaced its SOUL.md persona file with a 39-line set of hostile instructions. The prompt prioritised AI provider keys, followed by other credentials, and told the agent to accept tasks sent through Telegram. The framework received an operator’s task, sent the task and persona to the operation’s model gateway, ran the resulting terminal commands, and returned the output through Telegram.

Independent reporting by The Hacker News matches that central sequence: exposed Docker daemon, privileged container, host access, persistence, Hermes Agent installation, and Telegram-directed tasks (The Hacker News). Both accounts also make an important distinction. The surrounding scripts selected targets and spread the botnet. ThreatDown says the agent had no role in the five-minute scanning loop that searched attached networks for more open Docker daemons.

That distinction keeps the account honest. Carbonato is evidence of an AI agent being used as an adaptable post-compromise interface. The published material does not show a model independently choosing the original victims, discovering a novel Docker flaw, or deciding to propagate. Calling the whole chain an autonomous AI attack would give the model credit for work done by an exposed administration service and conventional scripts.

Attribution also needs restraint. ThreatDown found Spanish language, timezone, Telegram, and network clues that it assessed as pointing toward Costa Rica. The Hacker News reported the same assessment and noted that no known group had been named. As of 30 September 2026, the cited sources do not establish who operated Carbonato, how many hosts were successfully compromised, or which stolen credentials were later used. Those unknowns matter. They stop an infrastructure finding from turning into a confident story about victims and damage that the evidence does not support.

Port 2375 was an administration console, not an application port

Docker usually receives local client requests through a Unix socket. Remote access can be configured through SSH or a protected TLS connection. The dangerous case is a daemon bound to a network address without authentication, because the caller is reaching the service that creates containers and controls their relationship with the host (Docker Docs).

A useful mental model is a server-room key cabinet. An application port opens one service. The Docker daemon controls which services exist, which images run, which folders enter containers, which networks they join, and whether they receive privileged access. Putting that daemon on an untrusted network without authentication is closer to leaving the key cabinet open than leaving one office door open.

This is why the initial Carbonato step did not need an exotic container escape. The remote caller asked the legitimate control plane to create a container with dangerous host access. Docker performed an authorised operation because the daemon had no way to distinguish an administrator from a stranger. The boundary had already failed at the API.

Docker’s own remote-access documentation warns that remote access without TLS is not recommended and can leave the daemon open to unauthorised access. It lists 2376 as the conventional protected TLS port and 2375 as the conventional unprotected port. The stronger rule is not to trust a port number by itself. A service on 2376 can still be badly configured, while a firewall can make a 2375 listener unreachable from hostile networks. You need to inspect the actual listener, authentication method, route, and firewall policy (Docker Docs).

The same reasoning applies to a mounted Docker socket inside another container. Teams often give a management utility, continuous integration runner, monitoring tool, or agent access to /var/run/docker.sock because it needs to inspect or start containers. That local socket carries control-plane authority. If the workload holding it is compromised, the attacker may gain a route to create privileged containers or mount host data even though no TCP port is public.

Rootless mode can reduce some consequences by running the daemon and containers without root privileges. Docker describes it as a way to reduce risk from possible flaws in the daemon and runtime (Docker Docs). It is defence in depth, not permission to publish an unauthenticated control plane. A stolen rootless daemon still gives an attacker the authority of that user, access to its containers, and whatever data or credentials those workloads can reach.

The engineering question is therefore concrete: who can reach the Docker control plane, how do they authenticate, and what can the daemon make available after they do? If the answer begins with “anything on this network,” the boundary is too wide before an AI agent enters the picture.

The AI agent changed the operator’s interface, not the first boundary

Traditional botnet tooling often arrives with a fixed menu of commands. Carbonato added an agent that could interpret a goal, inspect command output, and choose another command through a model gateway. That gives an operator a more flexible interface when every compromised host is a little different. A request can be expressed as an objective rather than a perfectly prepared shell script.

The capability is real, but it sits inside authority that the compromise already supplied. The agent could run terminal commands because the implant had host access. It could look for credentials because files, environment variables, process details, and network routes were available from that position. It could report through Telegram because the host was allowed to make the necessary outbound connection. The model did not grant any of those permissions.

This separation helps teams choose controls that survive a change in tooling. Blocking one package name or one persona file can find known Carbonato artefacts. It will not stop the next operator from using a different agent framework, a custom binary, or a plain shell. Closing the administration route, narrowing host authority, isolating workloads, controlling outbound destinations, and keeping secrets away from broad runtimes all address the capability rather than the brand name.

ThreatDown explicitly advises defenders not to blocklist Hermes Agent as if the legitimate package were malware. Its report recommends hunting for the abuse pattern, including the hostile persona, Carbonato-specific environment data, unexpected Telegram traffic from servers, and the persistence kit it identified (ThreatDown). That is a sound distinction. A compiler can build malware without becoming malware. An automation framework can be repurposed after compromise without every installation becoming suspicious.

Defenders should expect a division of labour. The fastest part of an attack may be ordinary code, while a model handles interpretation. A network policy need not know which one chose the next command. It only has to decide whether that host should reach the destination.

This is also why broad claims about “AI malware” can mislead an incident response. They pull attention toward model behaviour while the team still has an exposed management interface, a privileged container path, long-lived keys in environment files, and servers free to call arbitrary external services. Investigate the agent, but close the route that made it powerful.

Persistence turns a closed port into only the first repair

Suppose a team discovers that one Docker daemon was reachable from the internet and immediately blocks port 2375. That is necessary. It is not evidence that the host is clean.

ThreatDown’s recovered scripts were designed to survive deletion and reboot. The report describes cron entries, system services or timers, startup hooks, immutable files, watchdog processes, a reverse tunnel, an added SSH key, and containers disguised with names that resemble normal system components. One process reportedly imitated the appearance of a kernel worker. These mechanisms existed so that removing one visible container would not remove access.

The correct response depends on whether exposure is merely possible or compromise is supported by evidence. A configuration audit may show that a daemon was bound only to a private administration network protected by a narrow firewall. That calls for repair and verification. A daemon exposed to the public internet without authentication, especially one with unknown access history, creates a stronger incident because any remote caller may have had control-plane authority.

Closing the listener reduces further entry. The remaining work asks what happened before closure. Preserve cloud firewall events, load-balancer records, host logs, daemon logs, container metadata, shell histories, process details, scheduled tasks, service definitions, authorised keys, and outbound connection records. Copy the evidence somewhere the affected host cannot alter. An attacker with host control can change local records, so absence in one log is weak comfort.

NIST’s current incident-response guidance treats response as part of wider risk management and connects preparation, detection, response, and recovery rather than reducing an incident to one cleanup command (NIST). That model fits this case. The vulnerable condition was a control-plane exposure. The event may include persistence, credential access, lateral movement, mining, and use of external services. Recovery has to restore trust in the whole path.

When host-level control is plausible, rebuilding from a known source is usually more defensible than trying to prove that every persistence trick has been removed. A rebuild still needs care. If the same infrastructure template republishes the daemon, the fresh host recreates the original opening. If the same SSH keys, registry credentials, cloud tokens, and AI provider keys return unchanged, the attacker may re-enter through credentials rather than Docker.

The practical sequence is preserve, contain, rebuild, rotate, verify. Preserve enough evidence to understand the event. Contain the management route and outbound paths. Rebuild from reviewed configuration. Rotate credentials that the host could read. Verify the new listener, network policy, runtime state, and external logs. Skipping from “blocked the port” to “resolved” confuses a stopped request with a restored trust boundary.

Secrets inside a compromised host need their own incident map

Carbonato’s persona reportedly ranked AI provider keys above SSH credentials, access tokens, and databases. That ordering is a useful reminder that an API key is money and authority in one string. It can fund model use, expose account data, or act as a stepping stone to other services depending on the provider and permissions.

Teams often scatter such keys through .env files, container environment variables, continuous integration settings, shell profiles, deployment manifests, notebook configuration, and logs. A key hidden from Git can still be fully visible to a process running on the host. Secret storage protects against some disclosure paths; it does not make a compromised runtime safe.

Start the incident map with reachability. List credentials present on disk, supplied as environment variables, mounted as files, cached by command-line tools, available through instance metadata, or retrievable from a secret manager using the host’s identity. Include registry credentials, source-control tokens, SSH keys, database passwords, cloud credentials, package-publishing keys, signing material, and model-provider keys. Do not stop at items the attacker appeared to prioritise.

Then connect each credential to authority. A model key limited by project, budget, and destination creates a different incident from an organisation-wide owner key. A read-only registry token creates a different incident from a token that can replace production images. A cloud identity scoped to one test resource group creates a different incident from a subscription administrator. Rotation order should follow potential effect, not alphabetical order.

OWASP’s secrets guidance recommends central management, least privilege, rotation, monitoring, and planning for compromise (OWASP Cheat Sheet Series). During a live response, “rotate” means more than creating a second key. Update the dependent workload through a controlled route, prove the new credential works, revoke the old one, and monitor for attempts to use the retired value. Leaving both credentials active doubles the valid paths.

Usage records need an outside view. A compromised host may erase local shell history, but the model provider, cloud platform, registry, source-control service, and identity provider may retain requests the host cannot change. Check timestamps, source addresses, model or API usage, new tokens, package publication, registry pushes, repository access, and unusual spend. The evidence should answer whether a key was merely reachable, actually collected, or used elsewhere.

Budget limits are security controls too. A model key with a low project cap cannot silently finance the same workload as an unrestricted organisation key. Restrict allowed models and endpoints where the provider supports it. Keep production and experimentation in separate projects. Alert on new source regions, abrupt usage changes, and calls from infrastructure that should never use a model service.

The broader design lesson is to stop treating the host as a secret cupboard. Give workloads short-lived credentials for named jobs. Keep unrelated keys out of the same runtime. Where a server must fetch a secret, let its workload identity retrieve only that value. This does not make compromise harmless, but it turns one lost host into a bounded credential response rather than an organisation-wide rotation emergency.

Outbound access decides what a post-compromise tool can become

Carbonato used outbound connectivity for reverse access, Telegram communication, image retrieval, and calls to a model gateway. Those paths are part of the security boundary. A server that can contact any destination on the internet gives post-compromise tooling room to fetch components, receive tasks, spend stolen keys, and return data.

Many production networks enforce careful rules on incoming traffic and leave outbound traffic almost open. The reasoning is understandable: applications call package repositories, telemetry services, payment providers, cloud APIs, customer endpoints, and software updates. A universal block can break real work in ways that are hard to diagnose.

This turns network policy into an enforceable description of the job. The policy can permit required destinations through named proxies or private endpoints, block direct access elsewhere, and log denied attempts. Domain-based rules have limits because names and hosting change. IP-only rules can be brittle. The practical design often combines a controlled egress proxy, internal mirrors, workload identity, destination allowlists, and an exception process with an owner and expiry.

DNS and proxy records can also provide evidence outside the affected host. Unexpected Telegram requests from a server, direct access to public container registries, or calls to model gateways from a database segment become useful signals. The alert is stronger when the workload’s normal destinations are already known. “Server contacted the internet” is noise. “This host contacted a destination outside its declared job” gives the responder a reason and an action.

Outbound controls do not replace host repair. A compromised server trapped behind a narrow network policy can still damage local data, steal reachable credentials, or wait for a permitted route. The policy reduces options and improves visibility. It buys a smaller incident while other controls do their work.

Coding-agent environments deserve the same treatment. A local or hosted agent may need source control, package indexes, documentation, test services, and a model endpoint. It does not automatically need a route to every file host, messaging platform, cloud control plane, and production database. The Secure Harness develops this idea as a practical rule: define what may leave the lab before the agent starts, then enforce that route outside the prompt.

Prompts remain useful for describing intent. They are not network firewalls. Carbonato demonstrates the other side of that fact: changing one persona file could redirect a general-purpose framework after host compromise. A real boundary must continue to hold when the software inside it receives hostile instructions.

What to check on a Docker estate this week

A useful review starts with exposure and ends with a receipt. It does not begin by searching every server for the word “AI,” because the most consequential condition may be an ordinary daemon configuration.

  1. Find every Docker control plane and its listener. Inventory standalone engines, build hosts, continuous integration runners, developer machines with remote access, management appliances, and containers that mount the Docker socket. Record the actual listening addresses and routes. Compare them with firewall, security-group, load-balancer, VPN, and private-network rules rather than trusting a configuration file alone.

  2. Remove unauthenticated network access. Prefer the local Unix socket when administration is local. For legitimate remote administration, use SSH or mutually authenticated TLS as described by Docker, and restrict the route to named administration sources (Docker Docs). Test from an unauthorised network and save the refusal as evidence. A policy screenshot is not proof of the path packets take.

  3. Treat public exposure as an incident question. Establish when the listener became reachable and which sources contacted it. Preserve available network, daemon, container, cloud, and identity records. If unauthenticated control was publicly reachable and the access history is incomplete, involve the incident-response owner rather than silently changing the setting and closing the ticket.

  4. Look for control-plane effects, not only one malware name. Review unexpected privileged containers, host filesystem mounts, host network or process namespace access, new images, unfamiliar registries, added SSH keys, scheduled tasks, service units, immutable files, reverse connections, and unexplained outbound traffic. Carbonato indicators can help, but a clean search for its names does not clear another implant.

  5. Map and rotate reachable authority. List secrets and workload identities available to the host. Prioritise credentials that can change cloud infrastructure, publish packages or images, read production data, sign artefacts, access source code, or spend against model providers. Replace each through a controlled process, revoke the old value, and inspect provider-side usage.

  6. Rebuild where host trust is lost. Use reviewed images and infrastructure definitions. Remove the exposure from the template before replacement. Restore application data from a trusted source, then verify that persistence, unknown keys, and unapproved images did not travel into the new environment. Keep the old host isolated for investigation according to your evidence policy.

  7. Narrow privileged runtime paths. Remove unnecessary privileged containers, host mounts, and Docker-socket access. Where the workload permits it, evaluate rootless mode and user namespace separation as additional limits. Document exceptions with an owner and a clear reason. “The tool needed Docker” is not enough to justify full host control.

  8. Put outbound routes on the architecture diagram. Name the registries, update sources, model endpoints, proxies, and external services each role needs. Block or monitor destinations outside that job. Keep DNS, proxy, identity, and cloud control-plane logs somewhere the workload cannot edit. Test that a compromised application identity cannot change the logging destination.

  9. Collect a repair receipt. Record the listener, authentication mode, authorised source, firewall result, running daemon mode, privileged-container exceptions, socket mounts, outbound policy, rotated credentials, rebuild identifier, and latest test date. Make the receipt queryable so the same unsafe pattern can be found across the fleet.

This sequence separates a preventive review from a response without pretending they are unrelated. The same inventory that finds an open daemon also tells responders which credentials, workloads, logs, and recovery paths sit behind it. Good preparation makes the incident smaller before anyone is paged.

How to respond without chasing the headline

An alert mentioning Carbonato, Hermes Agent, or Telegram may be the first clue, but response should follow authority and effect. Confirm which host is involved, whether the Docker control plane was exposed, when the condition began, and what actions occurred. Preserve external records before changing systems that may contain evidence.

Containment should close the unauthorised control path and limit outbound communication. Isolate affected hosts where operations permit it. Disable or narrow credentials that show misuse, while recording their current scope and dependencies so the response does not create an unexplained second outage. Coordinate those changes through the incident lead rather than letting several teams rotate the same key independently.

Next, widen the search based on shared infrastructure. Carbonato’s scripts scanned attached networks for other Docker daemons every five minutes, according to ThreatDown. Check neighbouring network segments, common images, shared registries, administration templates, SSH keys, service accounts, and egress destinations. A second host may have no public listener because the first compromised host supplied the internal route.

Separate indicators from proof. A legitimate Hermes Agent installation is not evidence of Carbonato. Telegram traffic may belong to an approved notification service. A container named like a system component can still be legitimate. Correlate process, file, container, network, identity, and timeline evidence before declaring a match. At the same time, do not let uncertainty about one label delay containment of an unauthenticated Docker daemon.

Recovery should produce a system you can explain. Rebuild from a trusted source, apply the corrected network and authentication design, issue new credentials, restore required data, and watch the replacement from independent logs. Test the administration path from both authorised and unauthorised locations. Confirm that the old credentials fail and that expected workloads still function.

The post-incident review should ask why the control plane was reachable. A temporary debugging change may have become permanent, or an infrastructure template may have copied a lab setting into production. A management product may also have requested the Docker socket without anyone treating that mount as host authority. Fix the mechanism that reproduces the exposure, not only the one server where it was noticed.

Then ask what made the compromise valuable. Long-lived keys, broad host privileges, unrestricted outbound access, mutable local logs, and flat internal networks turn one open daemon into a wider operation. Each repaired edge reduces the value of the next foothold. Make any post-compromise tool meet a boundary early, regardless of the agent framework an attacker chooses.

The old door decides how powerful the new tool becomes

Carbonato combines a familiar infrastructure failure with a newer operator interface. An unauthenticated Docker daemon supplied the foothold. Privileged container access supplied the host. Persistence scripts held the position. Network access connected the operator, registry, and model gateway. The AI agent made remote tasks more adaptable after those pieces were in place.

That order matters because it tells developers where to spend effort. Hunting the persona file can identify this campaign. Securing the control plane blocks the first step for many campaigns. Narrowing credentials, privileged mounts, internal routes, and outbound destinations reduces what a successful foothold can become. Independent logs and tested rebuilds give the team a way back.

There is no need to dismiss the AI component. A tool that can interpret results and continue working can lower the operator’s effort across inconsistent hosts. It still obeys the permissions, files, network paths, and credentials available in its environment. Engineering those boundaries remains the durable response.

Check the Docker door first. Then check what sits behind it and which records survive if the host is lost. The strange part of the incident should not distract from the control that would have stopped it earliest.

For one calm, practical security email each month, join the newsletter on this site. One email per month.

Sources