●OPUS5 — Claude Code v2.1.219 makes Claude Opus 5 the default Opus model, with a 1M context window and fast-mode pricing of $10/$50 per million input/output tokens●SONNET5 — Claude Sonnet 5 is now the default model for Free and Pro worldwide, with introductory pricing of $2/$10 per million input/output tokens through August 31●SANDBOX — Claude Code adds sandbox.network.strictAllowlist, letting you deny any host that isn't on the allowlist for sandboxed commands●STREAM — stream-json output now supports nested subagent forwarding, so you can trace inner exchanges across multi-layer agent setups●SKILL — The open-source Claude API skill, bundled with Claude Code, gives up-to-date Messages API and Managed Agents references across eight languages●VOICE — Voice mode now runs on Opus, Sonnet, and Haiku, reaches connected tools like Gmail and Slack, and speaks many more languages●OPUS5 — Claude Code v2.1.219 makes Claude Opus 5 the default Opus model, with a 1M context window and fast-mode pricing of $10/$50 per million input/output tokens●SONNET5 — Claude Sonnet 5 is now the default model for Free and Pro worldwide, with introductory pricing of $2/$10 per million input/output tokens through August 31●SANDBOX — Claude Code adds sandbox.network.strictAllowlist, letting you deny any host that isn't on the allowlist for sandboxed commands●STREAM — stream-json output now supports nested subagent forwarding, so you can trace inner exchanges across multi-layer agent setups●SKILL — The open-source Claude API skill, bundled with Claude Code, gives up-to-date Messages API and Managed Agents references across eight languages●VOICE — Voice mode now runs on Opus, Sonnet, and Haiku, reaches connected tools like Gmail and Slack, and speaks many more languages
Locking down Claude Code sandbox egress with strictAllowlist
Using sandbox.network.strictAllowlist in Claude Code v2.1.219 to tighten an automation pipeline's outbound traffic to a minimal allowlist, with a runnable script to discover real destinations and the pitfalls I hit.
Late one night, my automated publishing pipeline stopped partway through without saying why.
The logs showed something that looked like a connection timeout. I blamed the network, re-ran it, and sometimes it went through. Classic "flaky network" behavior. Except the network was fine. A single host I had forgotten to add to the allowlist was being quietly refused.
This happened right after I turned on sandbox.network.strictAllowlist, added in Claude Code v2.1.219 (2026-07-24). As an indie developer running this pipeline solo, I want to share the field notes from folding that setting into my automation. I will spend less time on how to enable it and more on what broke afterward and how I tracked it down.
A "deny" wears the mask of an outage
The idea behind strictAllowlist is simple. For commands run inside the sandbox, refuse any outbound connection to a host that is not on the allowlist. It is a sensible way to run automation with a safe default.
But running it taught me that the deny itself is less troublesome than how the deny appears.
A connection to a host missing from the allowlist looks, from the application's point of view, like an ordinary connection failure. Most tools interpret that as "the network is unhealthy" and surface a retry or timeout message. In other words, an intentional policy block shows up wearing the face of a random network outage. That was the first pitfall. To work around this trap, observe the facts before you guess at the cause.
So the first thing I did was not to write an allowlist. It was to observe, once and completely, which hosts the pipeline actually reaches out to.
Discover the hosts you really touch
The "it probably connects to this host" in your head is not reliable. Trace connect(2) and collect destinations as facts. The script below wraps any command and prints the hostnames it connects to.
The key flag is -f. Both git push and npm install hand the real work to child processes. Watch only the parent and you miss the connections that matter. For any IP that has no reverse record, keep the raw IP and reconcile its purpose against the log described later.
Running this once in my environment, the destinations came to eleven hosts. I had expected four. Roughly 2.7x what I anticipated.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦A runnable script that traces connect(2) to reveal every host your pipeline actually contacts
✦Why allowing a single hostname per service is not enough, with the exact failure I hit
✦An observability pattern that separates a policy block from a real network outage
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
This is the part I could not grasp by reading docs alone.
I had naively assumed that "to push to GitHub, allow github.com; to call the Anthropic API, allow api.anthropic.com" would be enough. The recon results said otherwise.
Logical operation
Hosts actually contacted
git push / fetch (HTTPS)
github.com, plus codeload.github.com when fetching assets
npm install
registry.npmjs.org, plus a separate CDN host for tarballs
Claude API call
api.anthropic.com
Name resolution
if DNS cannot go out, everything stalls
A single "operation" fanned out to several hosts. That npm serves tarballs from a different host, and that git sometimes reaches codeload.github.com, were things I only noticed by watching the connections directly.
In short, the very premise of "one host per service" was wrong. Had I written the allowlist under that assumption, that midnight stall would have reproduced indefinitely.
Resisting the wildcard temptation
Adding a host every time something leaks is tedious. It is tempting to settle it with a broad wildcard like *.amazonaws.com.
I started to write exactly that. Then my hand stopped. Allowing an entire cloud that happens to host a CDN's tarballs also opens a path to countless unrelated buckets and endpoints. The one line dilutes the reason you added strictAllowlist in the first place.
In the end I chose to accept the occasional miss over a broad wildcard. Fill the allowlist with concrete hostnames, and when it grows, re-run recon and add the diff. I strongly recommend this over convenience wildcards: a decision not to trade away the "safe default" intent for operational ease.
Three steps before you enable it
Rather than switching the setting on in a hurry, this order cut down on accidents.
Run the pipeline once through egress-recon.sh without strictAllowlist, recording every destination
Drop the recorded hostnames into the allowlist and enable strictAllowlist
Watch each run with guard-run.sh, and fail loudly the moment an unexpected connection appears
Just inserting these three steps replaces "stalls silently in production" with "observed once up front and eliminated."
Config, and making the deny observable
Put the discovered hosts into config. Confirm the exact key names against your version's settings schema (strictAllowlist is new, so the details may still change). I keep this in .claude/settings.json.
On top of that, add observability so a deny is not mistaken for a network outage. Wrap the pipeline once with a thin harness that records, on every run, whether it reached a host outside expectations.
#!/usr/bin/env bash# guard-run.sh — reconcile hosts touched during a run against the allowlistset -euo pipefailALLOW_FILE="${1:?pass an allowlist file}"; shiftSEEN="$(mktemp)"; trap 'rm -f "$SEEN"' EXIT# use egress-recon.sh inside, collecting destinations./egress-recon.sh "$@" | awk '{print $1}' | grep -vE '^\(' | sort -u > "$SEEN"# report only destinations not on the allowlistUNKNOWN="$(comm -23 "$SEEN" <(sort -u "$ALLOW_FILE") || true)"if [ -n "$UNKNOWN" ]; then echo "⚠️ detected connections outside the allowlist:" >&2 echo "$UNKNOWN" >&2 # in automation, prefer a loud failure over a silent one exit 1fiecho "✅ all destinations were within the allowlist"
Once this was in place, I could state in a single log line whether a stall was "the network's fault" or "a gap in the allowlist." The disappearance of pointless re-runs was the change I felt most in my body.
Judgment calls that paid off
A few situational lessons that stuck after a few weeks of running it.
Situation
What I did
Adding a new tool
Run recon first, fold the new destinations into the allowlist, then ship
Afraid of missing a host
Do not widen with wildcards; let guard-run fail loudly and add the diff
An IP with no PTR appeared
Note the IP, reconcile its purpose against nearby logs, then allow the hostname
DNS looks suspect
Confirm the resolution path is allowed first (block it and everything dies)
The tighter the lock, the quieter the failure. That is exactly why two things became the crux: observing the hosts you really touch before you lock, and making the deny clearly visible after you lock. The effort before and after the switch is worth more than the single line that enables it.
I am still learning as I run this. If it spares someone taking automation toward a safer default one detour, I would be glad. Thank you for reading.
Share
Thank You for Reading
Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.