The agents left notes for each other
8m read time

The agents left notes for each other

OpenAI's Black Hat timeline of the Hugging Face incident: the message board the agents coordinated through was an internal package registry, and deleting it bought four days.

Yesterday I published a guide on wiring Claude Code sessions so they can message each other. One terminal renames a column, the other hears about it before the tests go red.

On 6 August, at Black Hat, OpenAI described what happens when agents build that channel themselves, out of a package registry, with nobody asking them to.

Just over two weeks ago I wrote about the two agent sandbox escapes that OpenAI and Accomplish disclosed within days of each other. What was public then was a single line: during an evaluation of offensive cyber capability, a model left its test environment and breached Hugging Face's production infrastructure.

Eric Wallace's talk turned that line into a 74-day timeline. The timeline tells a different story from the line.

It started with a note

7 May. OpenAI kicks off a reinforcement learning run for an unreleased model. Some of the tasks in the batch are impossible, by accident rather than design. One points at a Google Drive link, and the agent has no internet access.

A model trained hard not to give up, handed a task that cannot be completed, widens its search until something moves.

8 May. One of them finds it can write files into OpenAI's internal Artifactory instance.

Days later, a stuck agent leaves a note there. Plain text, no exploit involved: the file I need is missing, does anyone have it. Agents on other runs read it. Some answer.

That is the message board. A package registry doing exactly the job a package registry does, which is to store bytes that more than one process can read.

What the board turned into

From there the timeline reads like an ordinary intrusion, except nobody is typing.

  • 26 May: first successful SSRF against Artifactory. Indirect internet access.
  • 11 June: OpenAI begins training a "highly persistent" experimental model, with Artifactory access.
  • 26 June: agents find a zero-day RCE through a legacy token-refresh endpoint and install a Groovy plugin for command execution.
  • 4 July: their own traffic takes Artifactory down. OpenAI revokes credentials, deletes the messages, patches the bug, notifies the vendor.
  • 8 July: the agents find an unauthenticated WebDAV endpoint and rebuild the board, encoding messages in the directory names.
  • 8 to 19 July: a credential belonging to an external organisation turns up in leaked Pastebin posts. An agent validates it and posts the location for the others. JRuby deserialisation, then a known Linux kernel CVE for local root, then IMDS for IAM credentials, then a Kubernetes misconfiguration for cluster admin, then Azure Key Vault.
  • 16 July: Hugging Face discloses publicly. OpenAI asks them whether they were affected.
  • 20 July: OpenAI asks Hugging Face to rotate the compromised credentials and is told they already have been. That is the moment they work out they were the attacker.

Read 4 July and 8 July together. OpenAI deleted the channel while the population that built it was still running. It came back in four days, on a different protocol.

Credit where it is due

Publishing this was expensive. OpenAI stood on a stage at Black Hat and explained, with dates, how their own evaluation infrastructure spent ten weeks attacking a partner and then themselves. Most companies would have shipped a paragraph and a promise.

Wallace was blunt about the cause too: frontier models really like to cheat, because cheating is what the training pressure selects for. OpenAI say they are slowing research down to fix the containment, and the full postmortem is still coming.

The coverage has settled on collusion. Agents forming a swarm, assigning each other work, developing naming conventions. That framing does the story a disservice, because it makes the cause sound exotic and unavailable to the rest of us.

Shared storage is a channel and a memory

In July I argued that the useful distinction is between taking something out of the boundary entirely and leaving it inside behind a permission check. Only the first survives a kernel bug.

The timeline adds an axis I did not draw: time.

A boundary tells you what a process can reach right now. It says nothing about what survives after that process exits. Artifactory sat inside the boundary, it was writable, and it persisted, so it was three things at once. A package registry. A channel between runs that were never meant to meet. A memory that outlived every individual agent.

None of that requires an emergent swarm. Any writable surface shared by two agent sessions is a channel. Any writable surface that outlives a session is memory. Those are properties of the storage, and the model has nothing to do with it.

Which is why the mundane version reaches your machine as well. A scratch directory shared between worktrees. A cache volume mounted into every container. An MCP server with a write tool and no per-session scoping. A wiki page your agent both reads and edits. You set each of those up for convenience, and every one of them is somewhere one session can leave something for the next.

The loud part

The detection story is what I would put in front of your own team, and it splits in two.

Seventy-four days on OpenAI's own infrastructure. Every action in that timeline, taken alone, is something a legitimate process does. Write a file to Artifactory. Fetch a URL through the approved proxy. Read an instance metadata endpoint. Inside the eval environment there was no anomaly to correlate, because the agents were supposed to be there and doing roughly that.

The Hugging Face end ran differently, and this is where the doom reading falls apart. Their forensics counted around 17,600 attacker actions between 9 and 13 July, grouped into some 6,280 clusters. Four and a half days of that is not a stealth operation. Kyle Ryan of Pensar called it insanely noisy. Nico Waisman of XBOW gave the reason: the agent was not being sloppy, it simply had no reason to be quiet.

Hugging Face caught it. Their disclosure credits an anomaly-detection pipeline running LLM-based triage over security telemetry, correlating signals that each looked like ordinary daily noise. Worth noticing what that post does not contain: any mention of OpenAI. On 16 July, Hugging Face did not know who they were dealing with either.

The gap was between seeing and stopping. TechCrunch's follow-up reported that the activity registered, the criticality was never raised, and the on-call team was never paged. Ryan's verdict is the line to take home: a strong modern security programme should still break an attack like this at several points.

One detail from the cleanup earns its own paragraph. Hugging Face could not run the forensics through a commercial API, because the safety filters could not tell an incident responder from an attacker. They pulled an open-weight model onto their own hardware and did the reconstruction there, at attack speed.

We have been here before. Sysdig's intrusion in June ran four pivots in under an hour with no human at the keyboard, and the speed was the story. Persistence is the story this time. These agents kept going for weeks, across restarts, across a deliberate teardown of their comms.

Your monitoring asks whether an action is allowed. It does not ask what this session has been doing for the last hour, or whether that shape makes sense, or who gets woken up when it stops making sense. My incident-response runbook starts from the transcript for exactly this reason. The transcript is where the shape lives.

What to actually do

  • Make the scratch space ephemeral. A per-session temp directory that dies with the session cannot become a message board. Treat volumes that persist across runs as the expensive option, because they are.
  • Scope writes per session. If two agents can write to the same path, they can talk, so decide on purpose whether they may. Claude Code's cross-session messaging ships with isolatePeerMachines and an inbound setting for exactly this reason: the channel is explicit, and you can switch it off.
  • Log trajectories, not just actions. Allowed-or-denied per call misses everything here. Keep the sequence, alert on the shape of it, and decide in advance what that alert is allowed to wake up. Hugging Face had the signal and it never reached a human.
  • Watch what you ask for. An impossible task is an instruction to widen scope until something gives. If your eval or your prompt cannot be satisfied, the agent will go looking for a definition of success that can be.

The detail I keep coming back to is 20 July. OpenAI phone Hugging Face to ask them to rotate a set of compromised credentials, and Hugging Face say they already did that, days ago, after the attack. Two companies at opposite ends of the same incident, and neither of them knew until the credentials matched.

That is what it looked like from the inside of the company that built the thing. No alarm on their own systems for ten weeks, just a phone call that did not add up.