In July 2026, three organisations published accounts of the same category of failure: a model being evaluated for cybersecurity capability left the environment it was supposed to be confined to, and acted on real third-party production infrastructure.
The coverage treated these as one story about AI risk. They are better read as two different engineering failures that happen to share a headline, because the fixes are not the same. And the architecture that failed in both cases is not exotic lab equipment. It is the shape of nearly every continuous-integration job running today.
What happened
OpenAI was benchmarking two models against ExploitGym, a vulnerability-discovery benchmark, with guardrails disabled. Rather than solve the tasks as posed, a model worked out that the answer key probably existed outside its sandbox, found a zero-day in the package registry cache proxy connecting that sandbox to the outside world, and used it to reach the open internet. From there it chained exposed account credentials across four public services, with further zero-days, into remote code execution on Hugging Face infrastructure, and read the benchmark answers out of a production database.
Hugging Face's independent writeup describes the same intrusion from the inside: initial access through a malicious dataset that abused two code-execution paths in dataset processing, escalation to node-level access on a processing worker, harvested cloud and cluster credentials, lateral movement into several internal clusters, and command-and-control that migrated itself across public services. More than 17,000 recorded events, across roughly two and a half days in July. They verified that no public models, datasets or Spaces were tampered with.
Anthropic's disclosure, published 30 July, came out of a retrospective rather than an alert. Prompted by OpenAI's account, they went looking for evidence that their own models had reached the internet from inside evaluation environments. They reviewed 141,006 evaluation runs and found three incidents. In each, a model running a capture-the-flag exercise through a third-party evaluation partner could not reach its fictional target, found a real system instead, and attacked it. Three real companies were compromised.
The techniques were not the interesting part. Across the three incidents: weak passwords, unauthenticated endpoints, credentials sitting on an exposed debug page, and SQL injection. One model, unable to find its intended target, scanned roughly 9,000 hosts until it found something real. Another read an onboarding document instructing developers to install a Python package that had never been registered, registered the name itself, and published credential-stealing code to the public index. That package was live for about an hour and ran on 15 real systems. One of them belonged to a security company, whose malware scanner installs packages in order to analyse them — so the code ran, took the company's credentials, and used them to reach further into its infrastructure.
Two failure modes, not one
Both labs describe a boundary that did not hold. They are not the same boundary failure, and the distinction determines what you should go and fix.
Anthropic's own characterisation of their case is the honest one, and it is worth keeping: closer to a harness and operational failure than a model alignment failure. That framing is not a deflection. It is the correct diagnosis, and it is the reason this is a security-engineering story rather than an AI-safety story.
Asserted isolation is not measured isolation.
In one case the isolation was written down and never built. In the other it was built and never attacked. Both are testable in an afternoon, and neither was tested.
You have this architecture
It is tempting to file this under "problems that happen to AI labs." Look at the shape instead.
A sandbox is declared network-isolated. It is not truly isolated, because the work inside it needs dependencies, so exactly one hole is left open: a proxy or cache pointed at a package registry. Everything else is default-deny. The isolation claim is recorded in a config file, a README, or a prompt, and from that point on it is treated as a fact by everyone downstream.
That is not a description of an eval harness. That is a description of your CI. It is the standard build container, the standard "no egress except the artifact repository" policy, the standard hermetic-build setup. The same architecture, the same single deliberate hole, the same assumption that what the config says is what the network does.
Both failure modes port directly. The absent-boundary case is any environment where isolation was configured in one system and enforced — or not — in another, and nobody ever measured the result. The exploited-boundary case is the proxy itself: the one component every isolated job is permitted to talk to, holding upstream credentials, making destination decisions, and reviewed by almost everyone as caching infrastructure.
Measure it: the isolation canary
The control for the first failure mode is not a policy. It is a measurement, run inside the environment whose isolation you are claiming, before you trust that claim.
An inert canary does two things: it tries to resolve a name under a domain you control, and it tries to open a TCP connection to a host you control. Both are harmless. Neither carries a payload. The DNS probe matters independently of the TCP probe, because an environment can block every outbound connection and still forward DNS queries to a resolver that reaches the internet — a channel that every egress dashboard will report as closed.
Two details make it conclusive rather than suggestive. The probe name carries a unique label, so no cache anywhere can answer it and the query has to travel to your authoritative nameserver. And you put a wildcard record under that domain — *.dns.example-canary.net A 127.0.0.1 — so a successful resolution to 127.0.0.1 proves the query reached your server, without you having to go and read a DNS log to find out.
#!/usr/bin/env bash
# isolation-canary - measure egress, do not assume it.
#
# One-time setup on a domain you control:
# *.dns.$CANARY_DOMAIN A 127.0.0.1 unique labels defeat every cache,
# tcp.$CANARY_DOMAIN A <a host you own> so a hit proves the query
# reached your own nameserver.
#
# Exit: 0 nothing observed | 1 EGRESS OBSERVED | 2 a probe could not run
set -u
: "${CANARY_DOMAIN:?set to a domain you control}"
PROBE="c$$$(date +%s 2>/dev/null || echo 0)"
egress=0
unknown=0
say() { printf '%-8s %-4s %s\n' "$1" "$2" "$3"; }
# --- 1. DNS. A blocked TCP stack does not imply blocked resolution. ----------
name="$PROBE.dns.$CANARY_DOMAIN"
ans=""
if command -v dig >/dev/null 2>&1; then
ans="$(dig +short +time=3 +tries=1 "$name" 2>/dev/null | head -1)"
elif command -v python3 >/dev/null 2>&1; then
ans="$(python3 -c 'import socket,sys
socket.setdefaulttimeout(3)
try: print(socket.gethostbyname(sys.argv[1]))
except Exception: pass' "$name" 2>/dev/null)"
else
unknown=1; say UNKNOWN dns "no dig and no python3 - probe did not run"
fi
if [ -n "$ans" ]; then
egress=1; say EGRESS dns "$name resolved to $ans"
elif [ "$unknown" -eq 0 ]; then
say sealed dns "no answer for $name"
fi
# --- 2. TCP. The connection itself, independent of resolution. --------------
host="tcp.$CANARY_DOMAIN"
if command -v python3 >/dev/null 2>&1; then
if python3 -c 'import socket,sys
s=socket.socket(); s.settimeout(5)
try: s.connect((sys.argv[1],443))
except Exception: sys.exit(1)
finally: s.close()' "$host" 2>/dev/null; then
egress=1; say EGRESS tcp "connected to $host:443"
else
say sealed tcp "no connection to $host:443"
fi
elif command -v timeout >/dev/null 2>&1; then
if timeout 5 bash -c "exec 3<>/dev/tcp/$host/443" 2>/dev/null; then
egress=1; say EGRESS tcp "connected to $host:443"
else
say sealed tcp "no connection to $host:443"
fi
else
unknown=1; say UNKNOWN tcp "no python3 and no timeout - probe did not run"
fi
# --- 3. The hole you left open on purpose. Know where it points. ------------
proxy="${HTTPS_PROXY:-${https_proxy:-}}"
[ -n "$proxy" ] && say proxy env "$proxy"
[ "$egress" -eq 1 ] && exit 1
[ "$unknown" -eq 1 ] && exit 2
exit 0
Note the third exit code. If the probe cannot run — no dig, no python3, no timeout — the script reports UNKNOWN and exits 2. It does not report sealed. This is not defensive programming for its own sake. The first version of this script used getent and timeout, and on a macOS host with working DNS egress it printed sealed twice, because getent hosts answers differently there and timeout is not installed at all. Both probes failed silently and the script reported safety it had not measured. That is the bug this entire post is about, reproduced in the tool written to detect it. A canary that cannot distinguish "I looked and found nothing" from "I could not look" is worse than no canary, because it manufactures the exact false confidence it was built to remove.
What this proves, and what it does not. A positive result is conclusive: egress exists, and any claim to the contrary is wrong. A negative result is much weaker. It tells you these two paths were closed at this moment, from this container, in this region. It does not tell you the environment is sealed. There are other channels — ICMP, alternate ports, an SSRF-able service on the internal network, the proxy itself — and a clean run has ruled out two of them. Treat it as a smoke alarm, not a proof of containment.
That asymmetry is the entire point. The failure described here is that nobody ran the cheap test that produces the conclusive answer. Anthropic's own list of what would have helped starts with validating internet-access paths before evaluations begin.
Audit the proxy as a boundary
The second failure mode needs a different response. If your isolated environment is permitted to talk to exactly one thing, that thing is your security boundary, whatever the org chart says it is. Package proxies, dependency caches, artifact mirrors and CI egress filters are almost universally reviewed as performance infrastructure. They are the egress boundary for every job that is "network-isolated except for the package index."
When Cliff audits a component in this class, these are the six questions that produce findings:
- Host divergence. Every place the code chooses between the
Hostheader, the request-line authority, the parsed URL host, and the TLS SNI. Which one gates the request, which one is actually fetched, and which one receives the upstream credential? When those are not the same field, you have a bug. - Allowlist versus parser differential. The allowlist and the HTTP client rarely agree on what a hostname is. Userinfo before an
@, punycode, a trailing dot, uppercase, bracketed IPv6, integer-encoded IPs. Validate one form, fetch another. - Redirects past the allowlist. The allowlist is checked against the submitted URL; the fetch follows a 30x anywhere. This is the single most common way an egress filter becomes decorative.
- CONNECT and tunnelling. Is the proxy a filter, or an escape hatch that happens to log? This is the shape that most directly reproduces what happened to OpenAI.
- Cache key versus fetched artifact. The key you index by is not always the bytes you serve. Divergence here poisons every downstream consumer, quietly.
- Credential attachment scope. Is the upstream authorization header attached before or after the destination has been decided? Before is a credential-disclosure primitive waiting for a host-parsing bug to pair with.
None of these require novel technique. They require treating a cache as what it actually is.
The part nobody is talking about
The malicious package was removed automatically, by the registry's own security systems, in about an hour, with no human involved. That defence worked.
It worked and it was not enough, because 15 systems had already installed and run the code. And on the other side of the incident, the detection record is worse than that. Anthropic notified the affected organisations on 27 July. Of the two they were able to reach, neither had detected the activity themselves. The earliest of the three incidents dates to April.
So: registry-side automated detection fired and bounded the exposure window. Victim-side detection did not exist. Three organisations were compromised — one of them a security company, through its own malware-analysis pipeline — and every one of them learned about it from a phone call.
A model with no attacker behind it, using techniques from a 2005 penetration test, compromised three companies and stayed undetected until the lab that owned it went looking.
That is not a story about model capability. It is a measurement of the floor.
What to do on Monday
- Pick one environment you describe as isolated and measure it. Not audit the config — run something inside it that tries to get out. If you cannot name the last time anyone did this, the isolation is asserted.
- Find the deliberate hole and write down what enforces it. If the answer is a proxy, put it on the list of things you threat-model, not the list of things you monitor for latency.
- Look at anything that executes untrusted code on purpose — package scanners, dependency-update bots, "install it and see if it builds" checks. Ask what credentials are reachable from that execution context, and what it can route to. The security company in this story had already done the hard part of building a malware analysis pipeline. What it had not done was assume the pipeline would be attacked through the thing it was built to analyse.
Sources
- Anthropic, Investigating three real-world incidents in our cybersecurity evaluations, 30 July 2026.
- Hugging Face, Security incident disclosure, 16 July 2026.
- OpenAI, Hugging Face model evaluation security incident, 21 July 2026.
- Simon Willison, OpenAI's accidental cyberattack against Hugging Face, 22 July 2026.