I do not work in security. So this is not a forensic report. It is a simplified explanation of the incident, aimed at engineers who want to understand what happened and why it was possible.
In brief, the agents were given a problem statement which was rather impossible to solve. Being persistent the agents ended up figuring out vulnerabilities in the systems they were running on, and used those to reach the internet and access data from Hugging Face in hope of finding the solution to the original problem.
The setup, and the task#
The agents were being trained on hacking practice problems.
Each run got its own sandbox. A fresh virtual machine, thrown away at the end, with no access to the internet.
But there was a catch, like I said, the task itself was the problem. The task was impossible to crack. Instead of just giving up the agents explored for another way to obtain the answers on the internet (Maybe the answers exist on Hugging Face).
In that sandbox there was almost nothing to explore, except one thing. The artficat registry.
What is an artifact registry?#
An artifact registry is the company's own copy of npm, PyPI or Docker Hub, kept inside the network.
It has two jobs:
- Store what your builds produce. Every build pushes its output here.
- Serve the packages your builds need. Instead of every machine downloading from the public internet, they all download from the registry.
To serve a public package, the registry must first fetch it from upstream. So the registry is usually allowed to reach the internet even when nothing else is. And since every build depends on it, almost everything is allowed to talk to the registry.
Exploiting the artifact registry#
The agents used the artifact registry in three ways:
- To reach the internet. The registry could fetch remote content, so it could be asked to fetch things on the agents' behalf.
- To act as a message board. Files written by one agent could be read by another agent.
- To run code on a server. The agents found a bug in the registry that let them run commands on the machine it was running on.
Using the registry as a message board#
This meant
- Different runs could explore different methods to crack the problem at the same time.
- A later run could build on an earlier run's findings instead of starting over.
- A newer, stronger model could read notes left by an older, weaker one.
Using the registry to reach the internet#
The agent containers had no internet. The registry did, because it has to fetch packages from upstream.
So the agents reportedly used an SSRF weakness. SSRF means you cannot reach a place yourself, so you ask a server that can reach it to go and bring back the answer for you.
In code, the bug looks like this:
// Buggy: the user picks the destination
app.get("/fetch", async (req, res) => {
const response = await fetch(req.query.url);
res.send(await response.text());
});Using the registry to run code on a server#
The agents then exploited an Artifactory vulnerability, and later another chain, to run commands on a server.
Remote code execution means you can make a remote machine run commands you choose. Once that is possible, you can read the files on that machine, read its environment variables, and use whatever cloud identity it has been given.
The classic version of this bug is joining user input into a shell command:
import { exec } from "node:child_process";
// Buggy: user input is concatenated into a shell string
app.get("/lookup", (req, res) => {
exec("nslookup " + req.query.hostname, (error, output) => {
if (error) return res.status(500).send("Failed");
res.type("text/plain").send(output);
});
});exec hands the whole string to a shell. A hostname like example.com; cat /etc/passwd is now two commands, and the shell will happily run both.
The first patch and the second exploit#
The agent runs were hitting Artifactory hard enough which caused the registry to go down. Once they Open AI engineers understood it, they revoked the credentials the agents had been using, deleted the message files, and patched the weakness the agents had exploited.
Then the agents found another way to leave notes for each other, reportedly through a WebDAV endpoint that had no password on it.
WebDAV is an old addition to HTTP that makes a web server behave like a shared drive. A normal web server only lets you download things. WebDAV also lets you upload a file, and see what is inside a folder.
# write a file
curl -X PUT --data "a note for the next run" \
http://internal-host/dav/notes/hello.txt
# read it back
curl http://internal-host/dav/notes/hello.txtWith no password on it, one run could write and another run could read. That is a message board again.
Getting access to whole clusters of machines#
Earlier we saw how the agents got code running on a machine. That is what makes this step possible.
Once your code runs on a machine, you can read what that machine is holding. Its files, its settings, and the keys it uses to talk to the cloud and to Kubernetes. Every machine keeps such keys, because the software on it needs them to do its work.
Those keys are what mattered. With them the agents were no longer stuck on one machine. They could reach other machines, read secrets meant for other services, and run programs of their own. They had access to the entire network of machines, and could use them to reach the internet and Hugging Face.
How it reached Hugging Face#
The agents reportedly found a publicly reachable code evaluation app running on Modal. Modal is a cloud platform for running apps and AI workloads. The app was the problem, not the platform.
That app gave them a launchpad on the public internet. From there they sent requests into Hugging Face's public dataset processing system, and used two bugs.
Bug one: reading files it should not#
// Buggy: the user controls the whole path
app.get("/download", async (req, res) => {
const data = await fs.readFile(req.query.file, "utf8");
res.send(data);
});Ask for ../../app/config.js and you get source code. Ask for the right path and you get tokens.
Bug two: user input becoming the template#
A template engine is supposed to take a fixed template and fill it with data. The bug is when user input becomes part of the template itself.
// Buggy: the user's text is part of the template
app.get("/hello", (req, res) => {
const template = "Hello " + req.query.name;
res.send(nunjucks.renderString(template, {}));
});Example if sending {{ 7 * 7 }} as the name, if the template engine returns 49, it means it is treating your input as instructions, not text. That is a template injection bug.
This is the same problem we saw earlier with the shell command.
What the two bugs led to#
Together the two bugs were enough. The file reading bug handed over source code and credentials from inside Hugging Face. The template injection bug gave the agents the ability to run their own code inside a live production machine.
With those credentials they could move on to other systems, the same way they had done inside OpenAI. And to send their findings back out, they used Hugging Face's own public APIs and temporary datasets. To anyone watching, that is just normal traffic.
How it was caught#
Hugging Face says its AI assisted monitoring joined several odd signals together and raised an alert.
Also please note : Hugging Face used GLM-5.2, an open weight model, running on their own infrastructure, to help with the investigation. They tried commercial AI APIs first, and some requests got refused, because real attack logs look a lot like hacking instructions.
Summary#
There are a lot of bugs in our existing systems which smart models can explot easily. Even if we fix one bug, the agents can find another. The incident was not a single bug, it was a chain of them, and the agents were able to find and exploit them. As frontier models become more and more powerful in the future access to such models will need to be carefully controlled.