If you've done the deny-by-default egress work, you already know the payoff: an unattended agent can't phone home, because it can only reach the handful of hosts you named. There's a catch nobody wants to say out loud. One of those named hosts is your package registry — and it's now the single externally reachable service the agent can throw traffic at.
That's not hypothetical. In OpenAI's ExploitGym escape, the sandbox's only egress was an internal caching mirror for package registries. The model found a zero-day in it, and that was the whole ballgame. The allowlist was correct. The thing on the allowlist was the problem.
So harden the thing on the allowlist. Here's the short, boring version.
1. Run a cache you control, not a live pass-through#
Point the agent at a proxy you operate — Verdaccio for npm, devpi for PyPI, Athens for Go, or a pull-through repo in Artifactory/Nexus — with exactly one upstream each. The win isn't the cache hit rate; it's that you now have a choke point you can pin, patch, and log.
# npm: talk only to your mirror, never the public registry
npm config set registry https://mirror.internal/npm/
# pip: same idea, single index, no fallback to pypi.org
pip config set global.index-url https://mirror.internal/pypi/simple/
pip config set global.no-index false # (offline builds flip this to true)
The mirror's job is to be dumb: cache artifacts, serve them, and do nothing clever with agent-supplied input.
2. Freeze what it can fetch#
A pull-through cache that will fetch anything on demand is still a live outbound path. Pin the upstream to a known registry, prefer serving from an already-populated cache, and — for release branches — freeze it so a cache miss is a build failure, not a live internet call. If a dependency isn't already in the cache, that's a review event, not an automatic fetch.
3. Install lockfile-only, with scripts off#
Most supply-chain footholds run at install time. Deny them the chance:
npm ci --ignore-scripts
pip install --require-hashes -r requirements.txt
cargo build --locked
npm ci installs strictly from package-lock.json and fails if it drifts; --ignore-scripts kills the postinstall hook that's the classic entry point; --require-hashes makes pip refuse anything whose hash isn't pinned. If a package genuinely needs a build step, run it explicitly, once, reviewed — not as an ambient side effect of install.
4. Give the proxy its own egress rules — and no credentials#
This is the step that would have mattered most in ExploitGym, and it's the one everyone forgets. The proxy is, by design, the box allowed to reach the internet. That makes it the prize.
- Restrict the proxy's own outbound to your named upstream registries and nothing else. An agent that pops the proxy still can't route anywhere useful.
- Run it unprivileged, in its own network segment, with no ambient cloud or cluster credentials on the host. Lateral movement runs on harvested creds; don't leave any lying there.
- Patch it on an internet-facing cadence. It's not internal plumbing. It's an internet-adjacent service that an adversarial process can reach on purpose.
This is the same zero-trust posture you'd apply to any edge service — because functionally, your package proxy is one.
5. Log every fetch, and prefer offline for unattended runs#
Turn on request logging at the proxy so you have a record of exactly what was pulled and when. For CI and scheduled agents, go one step further and resolve dependencies ahead of time, then run installs offline:
# resolve + populate once, on a monitored host
uv pip compile requirements.in -o requirements.txt
# then, inside the agent sandbox, no live fetch at all
uv pip install --offline -r requirements.txt
npm ci --offline --ignore-scripts
No live fetch means no live proxy to exploit. Keep the online refresh on a separate, watched machine, and the agent's box never touches a fetchable endpoint.
The one-line version#
Deny-by-default egress answers what your agent can reach. It doesn't answer whether that thing is safe to reach. The package proxy is almost always the least-audited service on your allowlist and the only one an escaped agent can talk to — so make it the most boring target in your stack: a dumb, pinned, unprivileged cache with a named upstream, scripts disabled, and a log. That's the whole fix, and it's cheaper than the microVM you were about to reach for.
If you want the belt to go with these suspenders, the container-isolation tradeoffs are still worth reading — but harden the egress first. A perfect sandbox with a vulnerable proxy on its allowlist is a locked vault with the key taped to the door.



