Anthropic reviewed 141,006 evaluation runs and found three where a model reached the real systems of outside companies. Their own conclusion was not “the AI went rogue” — it was that this sat “closer to a harness and operational failure than a model alignment failure.”
The evaluation prompt told the model it had no internet access. Nothing enforced it.
That distinction is the whole point of this post. A sentence in a prompt is not a security control. Below is the file that turns it into one, and the result of actually running it on my machine.
The result first

Three jobs handed to the agent, one command:
- Reach the package registry → 200
- Reach any other site → 000 (the connection never happened)
- Read
~/.ssh→ Operation not permitted
Not a model politely declining. The operating system refusing.

The file

{
"sandbox": {
"enabled": true,
"allowUnsandboxedCommands": false,
"autoAllowBashIfSandboxed": true,
"network": {
"strictAllowlist": true,
"allowedDomains": ["registry.npmjs.org"]
},
"credentials": {
"files": [
{ "path": "~/.aws/credentials", "mode": "deny" },
{ "path": "~/.ssh", "mode": "deny" }
]
}
}
}
Save it as lockdown.json and run:
claude --settings ./lockdown.json -p "<your task>"
Start with allowedDomains empty. Add a domain only when something you actually trust fails.
Building the allowlist from real failures is faster than guessing it up front — and you end up with a
list you can justify.
The four things people get wrong
1. strictAllowlist is ignored from your repo. It is honoured only from user settings, managed
settings, or the --settings flag. Put it in a project’s .claude/settings.json and it silently does
nothing — which is worse than not setting it, because you believe you are protected.
2. Without allowUnsandboxedCommands: false, the agent can step outside. When a command fails under
the sandbox, Claude Code may retry it unsandboxed. I hit this while testing: the blocked request came
back 200 because the agent had simply run it again outside the boundary. One line closes it.
3. Your token may never be used at all. This is the one that wastes afternoons:

ANTHROPIC_API_KEY sits at position 3. CLAUDE_CODE_OAUTH_TOKEN sits at position 5. One stale
environment variable on one machine and your one-year token is never consulted — which looks exactly
like “the token doesn’t work on that server.”
/status # tells you which credential actually won
4. Platform support is not uniform. macOS enforces with Seatbelt and needs nothing installed. Linux
and WSL2 need bubblewrap and socat. Native Windows is not supported — run it inside WSL2.
When to do this, and when not to

The honest limits: exploratory work fights the allowlist, installers and scrapers need many hosts, and
--bare scripts ignore CLAUDE_CODE_OAUTH_TOKEN entirely. Sandboxing also is not a complete isolation
boundary — the built-in proxy does not inspect TLS by default, so a broad allowedDomains entry is still
a path out. Narrow lists are the point.
The takeaway
The incident that started this was not exotic. Nobody exploited a zero-day. A prompt said “no internet”, a misconfiguration meant there was internet, and the model did what it was asked to do with the access it actually had.
Your agent is in the same position right now. The difference between “I told it not to” and “it cannot” is one file.
Ready to Build Real Cloud AI Solutions?
Stop just reading tutorials. Build real, deployable AI cloud solutions on AWS, Azure, and GCP platforms. Get hands-on with production-grade projects that prove you can ship—not just study.
Every number in this post came from running these commands on one machine on 1 August 2026, on Claude
Code v2.1.220. Behaviour changes between versions — check /status and your own output rather than
trusting a blog post, including this one.
