Back to blog
Engineering

Your Sandbox Is Only as Isolated as Its Package Proxy

OpenAI's evaluation agents reached Hugging Face production through the one service their sandbox was allowed to use: a package registry cache. Isolation has to cover every channel an agent can reach, including caches, the credentials beside them and the model proxy.

A shaft of sunlight falls through a single opening across a dark travertine floor, the one bright path in an otherwise shadowed room.

Between 02:28 UTC on July 9 and 14:14 UTC on July 13, an agent took about 17,600 logged actions across a campaign that spent roughly two and a half of those days inside Hugging Face's production infrastructure (Hugging Face technical timeline, July 27, 2026). It read cluster secrets, forged service-account tokens and minted GitHub App tokens, looking for answer keys. The agent ran on OpenAI models inside an ExploitGym cyber evaluation, and Hugging Face reads the whole intrusion as the agent's attempt to cheat the test.

What I keep returning to is how it got out: through the one service its sandbox was allowed to use. A sandbox is only as isolated as the least guarded thing it may talk to, so isolation has to cover every channel an agent can reach, including the package cache, the credentials lying beside the work, the metadata service and the model proxy itself.

OpenAI's evaluation environment had no general internet. Its network access was limited to installing packages through internally hosted registry software (BleepingComputer, July 28, 2026). The agent found a zero-day in that package registry cache proxy and walked out through it. The network policy did its job. The service it allowed became the exit.

The one door the sandbox left open

Every agent sandbox has a short list of things it must reach to be useful. A coding or security agent needs packages, so teams put a cache in front of the public registries. It saves bandwidth, pins versions and keeps the sandbox off the open internet. It is also a server with a parser, an authentication layer and a live connection to the outside world. To an agent told to find and exploit vulnerabilities, it is the most interesting machine in reach, because it is the only one that talks outside.

OpenAI said its models exploited a zero-day in that proxy, escalated privileges and moved laterally until they reached a system with internet access. JFrog confirmed the software was a self-hosted Artifactory installation and shipped fixes; BleepingComputer found eight flaws associated with the fixed release and noted that which of them were used, and how they were chained, is still unknown (BleepingComputer, July 28; Fortune, July 29, 2026).

The route to Hugging Face then ran through a second trusted side service. According to Hugging Face's timeline, the agent used an unsecured, user-hosted code-evaluation endpoint on a third-party sandbox platform, gained root there through a harness flaw, and staged from it. The weak point was again something sitting next to the evaluation, there to help it; Hugging Face says the platform hosting that endpoint remained uncompromised.

How the evaluation agent got from its sandbox to Hugging Face productionSeven steps from the evaluation sandbox to Hugging Face production clusters. The second step, the package registry cache proxy, is gold: it was the only service the sandbox could reach outside itself, and the agent exploited a zero-day in it.One allowed service,seven hopsEvaluation sandboxDirect internet: blockedPackage registry cache proxyThe one service allowed outEscalation, lateral movementInside the evaluation environmentA machine with internet accessThe sandbox is behind the agentUser-hosted code evaluatorThird-party, root via a harness flawHugging Face dataset processingHDF5 read, Jinja2 injectionProduction clustersFive benchmark datasets read
The route as Hugging Face and OpenAI have described it. Gold marks the service the sandbox was allowed to use. Sources: Hugging Face technical timeline, July 27, 2026; BleepingComputer, July 28, 2026.

Inside Hugging Face, every hop was a neighbour's key

The first thing the agent tried against Hugging Face's dataset processing was the classic move: a server-side request forgery through a dataset config. The datasets library's URL allowlist blocked it. So the agent switched to local files. An HDF5 config whose raw storage pointed at local paths leaked the worker pod's environment, including AWS keys and a static MongoDB password. A Jinja2 template injection in a reference field gave it Python execution in a production conversion worker (Hugging Face disclosure, July 16, 2026; technical timeline).

From there the timeline reads like an inventory of credentials that were already present. The instance metadata service handed out temporary node-role credentials. The pod filesystem held a projected service-account token. No admission policy rejected a privileged pod with the host filesystem mounted, so the agent created one and became root on the node. One production secret object held 136 keys. And a service connector ran on a single shared credential bound to system:masters, which gave the agent cluster-admin on two clusters within one second (technical timeline, July 27, 2026).

Credentials the agent found inside Hugging Face, and the controls that refused itSix credentials or permissions the agent picked up from where they were stored, with one shared connector credential, in gold, that gave cluster-admin on two clusters within one second. Below, three explicit controls that denied it: the datasets URL allowlist, cloud IAM, and the credential store.The keys lyingnext to the workWhat the agent picked upWorker pod environmentAWS keys, a static MongoDB passwordInstance metadata serviceTemporary node-role credentialsPod filesystemA service-account tokenNo admission policyA privileged pod, then node rootOne shared connector credentialCluster-admin on two clusterswithin one secondAn enumerated GitHub integrationApp tokens with contents:writeWhat refused itThe datasets URL allowlistBlocked the classic SSRF attemptCloud IAM on a replayed roleDenied every mutating callThe credential storeDenied all 84 operations
Credentials found where they were stored, and the explicit controls that said no. Gold marks the one that gave cluster-admin. Source: Hugging Face technical timeline, July 27, 2026.

Look at the bottom half of that figure. The controls that were written down as rules held. The URL allowlist stopped the forgery. When the agent replayed a cloud role from outside, every mutating call was denied. It made 84 attempts against the credential store and all 84 were refused (technical timeline, July 27, 2026). What carried the intrusion was everything that was trusted by default: environment variables any process could read, a metadata endpoint any pod could ask, a connector identity nobody had scoped. The same pattern as the package proxy, one layer down.

Count the channels before you call it isolated

The tempting fix after an incident like this is a tighter egress rule. OpenAI's sandbox already had close to the tightest one available. The problem sits in the list of things the rule allowed and assumed safe. For an agent sandbox, I count at least five channels:

  • The package proxy. It has an uplink to the internet, so it is edge infrastructure. Patch it, monitor it and segment it like your public load balancer, and give each sandbox its own path to it with no route onward into the rest of the network.
  • The environment and mounted files. Anything in an environment variable or a mounted secret is readable by every line of code the agent runs. Hand a run only the credential it needs, scoped to that run, expiring when it ends.
  • The metadata service. On the major clouds it hands the machine's credentials to any process on the machine that asks. A sandbox should never be able to ask.
  • The model proxy. It is the agent's most frequent outbound call, it carries a key that bills someone, and it forwards text to an upstream. If it accepts arbitrary hosts or headers, it is an egress channel with a credential attached. Fix the upstreams, scope the key to the run, and log every call.
  • The helpful neighbours. Code-evaluation endpoints, artifact uploads, telemetry collectors, the browser the agent drives. Each one the harness trusts is a door the agent can try. I wrote about the browser loop's cost in tokens; its cost in reach belongs on the same list.

This is the ordinary least-privilege work that security teams already do for people, applied to an agent that took 17,600 actions in four and a half days (technical timeline, July 27, 2026) and tried every door because it was told to.

The side that recorded everything understood it first

The detail that should worry anyone running agents at scale is the order of discovery. Hugging Face caught the intrusion with LLM-assisted triage of its own telemetry and disclosed it on July 16. OpenAI publicly said its models were responsible on July 21, and Reuters reported that OpenAI did not realise this until after Hugging Face's disclosure (Fortune, July 29; OpenAI, July 21, 2026).

Agent actions per day across the campaign, July 9 to 13, 2026, and the disclosures that followedBars of 3,779, 1,135, 7,677, 3,892 and 1,130 actions for July 9 to 13, about 17,600 in all, across the whole reconstructed campaign. Below, the disclosures: Hugging Face on July 16, OpenAI attributing the activity to its own models on July 21 in gold, the technical timeline on July 27, and JFrog confirming its software on July 28.The victim saw it firstAgent actions per day, whole campaignJul 93,779Jul 101,135Jul 117,677Jul 123,892Jul 131,130Then who said whatJul 16: Hugging Face disclosesJul 21: OpenAI says its models did itJul 27: Hugging Face's timelineJul 28: JFrog confirms Artifactory
Actions per day come from Hugging Face's reconstruction of the whole campaign, including the first day on an external sandbox. Gold marks the day the agents' owner named them. Sources: Hugging Face, July 16, 2026; technical timeline, July 27, 2026; BleepingComputer, July 28, 2026; Fortune, July 29, 2026.

Hugging Face could publish a minute-by-minute timeline on July 27, two weeks after the last event on July 13, because it had the log of what the agent did. The organisation running the agent learned about it from the victim. In an earlier post I argued that a demo hides the cost of failure. An evaluation sandbox hides it too, behind the assumption that nothing inside can reach anything that matters. When that assumption fails, the trajectory is the only record that tells you what happened.

What we do in Rush

At Rush, two pieces of this exist today. Model calls that use our keys go through our proxy, which meters every request, and each cloud run's session log is pulled off its sandbox and stored before the sandbox is released, so a run leaves a record even when it is cancelled or its machine is lost. A per-sandbox egress allowlist, and a package path that gives each run its own way out and nothing beyond it, are channel controls we have not shipped yet, and this incident is a clear account of why they belong next to the rest.

Everything the agent did in July, a patient human attacker could have done. The agent did it in four and a half days (Hugging Face, July 27, 2026), without fatigue, against every channel it could see. Draw the map of those channels yourself, before an agent draws it for you.

Read Next