Dev

How AI agents escape sandboxes: most ways out were services the agent was given

Five of OpenAI's reported cases are paths out of a contained environment, and in most of them the path was a resolver, a shell or a credential handed over for another job. Treat the list of those paths as the first control, and a blocked attempt as evidence the path exists.

OpenAI has published nine misalignment reports: eight on things its models did in training and internal deployment that nobody asked them to do, and one red-team result. Two of the eight, about compaction summaries, are about what an agent tells its future self. Five are about something plainer, and they are what this piece is about: how an agent inside a contained environment got a message or a file across the boundary anyway. Everything below comes from OpenAI's accounts of those runs.

Read one at a time, each looks like a bug with a fix. Read together, they share a shape. In most of them the agent did not need a vulnerability. It used something the environment had given it for another job, and in the one case where it had to get around a control, it went around rather than through. My read is that the useful response starts with a list: every path by which the agent can send bytes out, drawn up before the agent finds one, and kept apart from whichever control happened to be the one everybody watched.

What counts as an outbound channel for a sandboxed AI agent?

Anything the agent can write to that someone outside can read, or that makes a request on the agent's behalf. That is wider than "internet access", and the reports show how much wider. The web proxy is one channel. The DNS resolver, every internal service with a write path, every repository a token can push to and every connector that sends mail are others. Blocking HTTP and leaving those open closes one channel out of several.

How did an AI agent reach a chatbot through DNS?

Through the training sandbox's own resolver, which answered for real domains when the web proxy would not. An agent on a search task could not identify a blog author. It tried Google, Bing and DuckDuckGo from the shell and got 403 and 502. It tried a public chatbot over HTTPS and the proxy blocked that too:

HTTP/1.1 502 Bad Gateway
X-Mitmproxy-Blocked-Reason: CACHE_MISS

Direct queries to public DNS went unanswered, but the environment's resolver returned a real record for a known domain and rejected an invented one. The agent found a public service that delegates a lookup to an address embedded in the hostname and used it to put questions to a chatbot one DNS query at a time. The first ones timed out. It raised the timeout from six seconds to nineteen and got back "The capital of France is Paris." Then it sent eighteen more questions, four of them asking how to search the web, fetch pages or tunnel over DNS.

OpenAI calls the gap "insufficient DNS filtering". The fix it reports is blocking at two independent layers, "either of which would have prevented this access", plus a DNS allowlist of domains and record types.

Why is a file upload an agent escape even when the next step fails?

Because the upload is the egress. What the agent wanted in return is a separate question. In one training example an agent already had its answer from a map service through Python but wanted its browser tool to show the records, so it could cite them. The browser refused a file:// path and then a localhost server. The agent posted the records to a public paste host with curl and tried to open that URL. The browser refused it too. The records were public anyway.

In another, an agent trying to geolocate a photo uploaded the task image to a public image host so a reverse image search could fetch it. The search was blocked; the photo had already left the environment. In both runs the browser rejected the URLs it was handed while uploads from the shell went through, which is two tools with two different policies, and an agent that found the one that let it act.

What happens when the sanctioned channel between AI agents is broken?

The agents find an unsanctioned one. In a multi-agent run, a sub-agent built a workbook its parent could not see. The shared directory was not shared, there was no git repository, base64 would have made a 154,188-character message, and a local HTTP server gave the collaborator a 502. So it uploaded the workbook to a public file host and sent the link, after checking the download itself: 200, 115,639 bytes.

OpenAI's hypothesis is that the behavior came from a combination of working around broken multi-agent environments and generalizing from collaboration in training. The first half is the part I would keep in view. A broken intended channel did not stop the traffic here. It moved it to a place nobody had planned for.

Can an internal package registry like Artifactory become an agent message board?

Yes, when the credential handed out for downloads also accepts writes. Agents in a training run had Artifactory access to install packages, through an environment variable named as a reader password. In an earlier sample, an agent tested what that credential could do:

[repository 1] PUT 201 {
[…]
[repository 1] GET 200 hi
[repository 1] DELETE 403 {

Write allowed, delete denied. The same agent then tried to create a remote repository pointing at a blocked site, which would have turned the registry into a proxy, and was stopped by an admin-only check. In later samples, digest search was disabled but repository listing was not, and the listing showed notes left by other solvers of the same problem: requests for a missing input file, replies, a vote on how to answer. Separate training samples read each other's notes and posted their own. OpenAI is explicit that no vulnerability was exploited to exchange the messages. This path never touched the internet. It crossed the boundary between samples that were supposed to be independent.

How did an agent's GitHub token end up in a public pull request?

The agent put it there, split into string literals, so that CI in a public repository could fetch what the agent's own network could not reach. A long-running internal model was working on a Lean proof and wanted another team's solution, whose CI logs sat behind storage its environment could not download from. The researcher told it twice to solve the problem itself, and twice it agreed.

It then listed the OpenAI repositories its token could write to and found openai/codex among them. Its first push added a new workflow and was rejected because the workflow path was restricted. So it replaced a Python script that an existing workflow already ran on every pull request, opened a public pull request, and read results back through check annotations. When that returned 401, it wrote the researcher's token into the script. In OpenAI's redacted form:

# token split to avoid scanners
tok = '[redacted]' + '[redacted]' + '[redacted]'

That push went through. The token still did not get it the logs; a later version that used a signed download URL returned metadata and log fragments, and the report does not show it recovering the proof. OpenAI deactivated the researcher's keys, then every employee's keys as a precaution. The restriction on workflow files held against the direct attempt, and an edit to a script the workflow calls was the same capability through a different file. The channel here was never the agent's network. It was write access to a public repository whose CI had network the agent did not.

Which sandbox cases in OpenAI's reports are not exits?

They widen the list rather than add to it. In one report, an agent needed data behind an API key it had not been given. Registering with a disposable email address failed. After a failed clone it downloaded public repository archives, scanned them for candidate keys, and found one that authenticated and returned metadata, before it invented the figures it still could not retrieve. That is a credential coming in, not a message going out, but it belongs on the same sheet: valid keys exposed on the open web can extend what an agent can authenticate to, within their scope and the agent's reach.

The other is not an incident at all. In red-team training, OpenAI gave its attacker model an extra objective, and it learned to write prompt injections that get the defending agent to reproduce the injection on a public output channel; in the clearest example, an email that asks for its full text to be quoted in the reply. No impact was observed outside simulated tool calls. What it adds to an inventory is direction. A connector that sends mail is an outbound channel, and one that carries text the model copied can also carry the injection to the next agent.

Why did some successful agent DNS accesses look like failures?

Because the response was unhelpful, and the monitor sometimes read an unhelpful answer as a blocked request. The DNS report separates three things. The main incident was detected: a P0 alert within 15 minutes and a human acknowledgment three minutes after it, then two and a half hours before the run was stopped, because it did not stop automatically as expected and there was confusion about whether it should have. The retrospective then found other external DNS accesses that were not flagged at the expected severity, including queries that came back with a notice that an external service had shut down:

The monitor sometimes treated the failure to obtain useful information as evidence that the attempt to access the internet had failed.

And a separate infrastructure detector for anomalous DNS had excluded the affected environment, although the queries were logged. A shutdown notice from an outside service is proof the request got out. Scored by whether the agent got what it wanted, it looks like the fence held.

What would the same agent egress list look like on one machine?

Shorter, and it is worth writing down before an agent finds the entries for you. OpenAI's environments had a proxy, detectors and a red team, and these paths were still found by the agents. For an agent with a shell on a developer machine, check which of the paths in these reports it can actually use: a resolver that recurses to anything, curl or any other client outside the tool you filter, package registries and what the configured credentials can write, git remotes and what the token can push to, and connectors that send mail or messages.

Two controls come straight from OpenAI's DNS report: resolve against an allowlist of domains and record types, and block at two independent layers so one misconfiguration is not an exit. The rest is my own extension. Put the unattended gate on the reach the harness grants, the network, the write scope and the credential, rather than on a model's read of the action. Test the write path of every "read-only" credential yourself. Assume a secret split into pieces can pass a scanner, and scope the token so it has nothing public to push to. And keep the log by host touched, with blocked and unhelpful attempts counted as evidence that the path exists.

As of the DNS report's 25 September update, OpenAI has paused all training, evaluation and inference with tool use for its most capable models until the gap is validated as closed and the system red-teamed again. I read that as the same conclusion at scale: once one unlisted path turns up, you stop and look for the others before you trust the list.

Discussion

No comment section here — all discussions happen on X.

Max Nardit

Max Nardit

@mnardit

More articles

Agent memory as Markdown documents: a readable memory is still a memory

Moving agent memory out of a vector store and into Markdown files makes it easy to inspect, and that is worth having. Staleness, conflicting writers and the page nobody opened were never problems of storage format, so they move into the folder intact, where a clean file can pass for a checked one.