Agent isolation / layered sandbox
July 4, 2026 · View on GitHub
title: Agent isolation / layered sandbox status: stable kind: feature mode: infra source: > MISSION.md § Privacy, security and supply-chain integrity ("Clean- environment wrapper", "Layered sandbox by default", "Pinned, reviewed, signed dependencies"). Implemented in tools/agent-isolation/, tools/agent-guard/, tools/permission-audit/, tools/egress-gateway/, the setup-isolated-setup-* skills, and .claude/settings.json. acceptance:
- Every agent subprocess runs inside an OS-level sandbox with default- deny filesystem reads and network egress.
- Credential-shaped env vars are stripped before the agent execs.
- State-mutating shell calls (git push, and every gh command except allow-listed read-only ones) require a confirmation prompt; secrets/cred files are deny-read.
Agent isolation / layered sandbox
What it does
Runs every agent invocation inside a layered sandbox so that even a successful prompt injection cannot read credentials or reach a non-allowed host. The fallback when prompt engineering fails is the OS saying "no".
Where it lives
tools/agent-isolation/— the harness (clean-env wrapper + sandbox profiles).tools/agent-guard/— deterministic pre-execution guard dispatcher (stdlib-only). Wired as aPreToolUsehook (Claude Code) or atool.execute.beforeplugin (OpenCode); inspects every shell command before it runs and denies the ones that break a hard framework rule, independent of model memory. The guard decisions live in a single harness-agnosticdispatch()core so every wired harness enforces an identical rule set. Capability:substrate:action-guard.tools/permission-audit/— audits and atomically edits Claude Code'spermissions.allow[]entries in.claude/settings.jsonand.claude/settings.local.json. Backs the--apply-permission-auditflag of/magpie-setup verify(check 8d). Also handles OpenCodepermissionconfig viaaudit-opencode. Capability:substrate:sandbox.tools/egress-gateway/— local HTTP(S) forward proxy for egress control. Framework tools pointHTTPS_PROXY/HTTP_PROXYat it; the gateway rejects any connection to a host not on its allowlist before a socket is opened. Defence-in-depth per RFC-AI-0003: even a prompt-injection reaching for an arbitrary endpoint is blocked at the network layer. Capability:substrate:sandbox..claude/settings.json— thesandboxblock (filesystem allow/deny, networkallowedDomains,excludedCommands) andpermissions(deny/ask).- Skills:
setup-isolated-setup-install,-update,-verify,-doctor(probes live sandbox restrictions — SSH-agent reachability, localhost port binding, docker/podman socket — and maps each to a numbered troubleshooting entry; read-only, never modifies settings). docs/setup/secure-agent-internals.md— the three-layer model.
Behaviour & contract
The reference model is four layers, layered:
- Clean environment — a wrapper strips the process env to a
project-declared whitelist before exec (no
$GH_TOKEN,$AWS_*,$ANTHROPIC_API_KEYleakage). - Filesystem + network sandbox — Linux
bubblewrap+socatSNI proxy; macOSsandbox-exec. Default-deny reads outside the tree and egress to non-allowed hosts.sandbox.excludedCommandscarves out commands that need host auth the sandbox blocks —gh(OS keyring); the blast radius is held by layers 3 (gh auth token/gh auth refreshdenied) and 4 (ghwrites gated byask). - Tool permissions — the host's
permissions.denyblocks denied paths/binaries (Read(~/.ssh/**),Bash(curl *), …). - Forced confirmation —
permissions.askongit pushand, safe-by-default, onBash(gh *): everyghcommand prompts unless a more-specific read-onlyallowrule (gh pr view,gh * list, …) exempts it, so every destructive or unknownghsubcommand confirms.
Pinned system tools (bubblewrap, socat, agent CLI) are aged through a
cooldown window; bumps are PRs, not silent updates.
Out of scope
- The human-in-the-loop confirmation itself (that is the modes' job); this area provides the OS-level enforcement underneath it.
- Editing
.claude/settings.json(it is in thedenylist).
Acceptance criteria
- Filesystem and network default-deny with explicit allow-lists.
- The clean-env wrapper strips credential-shaped vars before exec.
git pushandBash(gh *)are inpermissions.ask(read-onlyghexempted viaallow); secret/cred files are inpermissions.deny.
Validation
uv run --project tools/agent-isolation --group dev pytest
uv run --project tools/agent-guard --group dev pytest
uv run --project tools/permission-audit --group dev pytest
uv run --project tools/egress-gateway --group dev pytest
python3 -c "import json,sys; s=json.load(open('.claude/settings.json')); \
asks=' '.join(s['permissions']['ask']); \
sys.exit(0 if 'git push' in asks and 'gh *' in asks else 1)"
Known gaps
stable; drift shows up when a new state-mutating command is added to a skill without a matchingaskrule — the plan pass flags it.tools/agent-guard/andtools/egress-gateway/are new additions since the last pilot cycle; end-to-end integration with a real adopter session has not yet been exercised.