Devcontainer Sandboxing skill for Claude Code
Skip prompts only inside a boundary you have tested. Claude matches the isolation to the risk: the built-in Bash sandbox for fewer prompts on a laptop, a dev container with a non-root user, persisted config and a default-deny egress firewall for teams, or a disposable container for unattended bypass runs. Every boundary is proven with escape tests, credentials stay scoped, and bypass mode is used only where the tests passed.
- name
devcontainer-sandboxing - size197 lines · ≈2,300 tokens
- files1
- licenseMIT
- updated
What Claude sees before loading it
This description is the only part of the skill that sits in context all the time. Claude reads it to decide when to load the full playbook below.
description: Runs Claude Code inside isolation boundaries chosen for the risk, from the built-in Bash sandbox (Seatbelt or bubblewrap with filesystem and network allowlists) to dev containers with the Claude Code feature, a non-root user, persisted config volume, and a default-deny egress firewall, to disposable containers or VMs for unattended --dangerously-skip-permissions runs. Covers managed settings in the image, credential hygiene, and escape tests. Use when the user wants Claude to run without prompts safely, asks for a devcontainer, sandbox, or network lockdown, or works with untrusted code. Try asking
- Set up a devcontainer so Claude can run with --dangerously-skip-permissions safely
- Turn on the Bash sandbox and lock network access to npm and GitHub only
- I need to let Claude work on an untrusted repo — what's the safest setup?
Use it when
- You want Claude to run long tasks without permission prompts
- A team wants one isolated, reproducible environment for Claude Code
- Network egress or filesystem reach must be restricted
- You are working with untrusted code or repositories
Skip it when
- You only need allow and deny rules for specific tools or commands — use permissions-policy instead
- You are hardening production container images rather than agent environments — use container-security instead
How to check the result
Escape tests pass as expected: an arbitrary site is unreachable, the Claude API is reachable, host secrets are absent, the user is non-root, and writes outside the workspace fail.
The common mistake it prevents
Mounting ~/.ssh, cloud credentials or the Docker socket into the container for convenience, which hands a bypass-mode agent everything the boundary was meant to protect.
Isolation is done when you have written down what the agent can reach (files, network, credentials), proved each boundary with a command that should fail and does, and matched the permission mode to that boundary. Skipping permissions is acceptable only inside a boundary you have tested; never on a host with your SSH keys and cloud credentials.
Pick the boundary
| Need | Boundary | Prompts |
|---|---|---|
| Fewer prompts on your laptop, trusted repo | Built-in Bash sandbox (/sandbox) | Auto-allow sandboxed commands |
| Same isolated toolchain for the whole team | Dev container + Claude Code feature | Normal or auto mode |
Unattended long runs, --dangerously-skip-permissions | Dev container or Docker with egress firewall, no host secrets | Bypass, inside the box only |
| Untrusted or unknown code, security research | Disposable VM or cloud sandbox, fresh per task | Bypass inside; destroy after |
| Org-enforced policy | Managed settings (server-managed or MDM), not repo files | Per policy |
Key limits: the built-in sandbox covers shell commands (Bash, PowerShell, Monitor) and
their child processes only; Claude’s file tools, MCP servers, and hooks run outside it.
A dev container confines everything Claude Code does, but anything mounted (the
workspace, ~/.claude credentials) and anything the network allows is reachable.
Option A — built-in Bash sandbox
/sandbox # panel: enable, mode (auto-allow vs regular prompts), overrides, deps
macOS uses Seatbelt; Linux and WSL2 need bubblewrap and socat
(sudo apt-get install bubblewrap socat). On Ubuntu 24.04+, AppArmor may block bwrap’s
user namespaces (sysctl kernel.apparmor_restrict_unprivileged_userns returns 1); the
sandbox docs give the AppArmor profile fix. Native Windows runs unsandboxed: use WSL2.
{
"sandbox": {
"enabled": true,
"failIfUnavailable": true,
"allowUnsandboxedCommands": false,
"filesystem": {
"allowWrite": ["/tmp/build"],
"denyRead": ["~/.ssh", "~/.aws", "~/**/.env"]
},
"network": { "allowedDomains": ["registry.npmjs.org", "github.com", "*.githubusercontent.com"] },
"excludedCommands": ["docker compose *"]
}
}
- Default writable: working directory, temp dir,
--add-dirdirectories. Grant extra write paths withfilesystem.allowWriterather than excluding a tool. allowUnsandboxedCommands: falsestops the “retry outside the sandbox” escape hatch.failIfUnavailable: truemakes Claude Code exit instead of silently running unsandboxed when dependencies are missing.excludedCommandsruns matches with full access; keep patterns narrow, because a tool that executes a file in the repo (compose files, Makefiles) lets Claude write then run.- Project settings cannot set
filesystem.disabled; user, managed, or--settingscan.
Probe: ask Claude to run touch ~/sandbox-probe — it must fail with “Operation not
permitted” (macOS) or “Read-only file system” (Linux). Decline any offer to retry
unsandboxed; delete the file if it was created.
Option B — dev container
- [ ] 1. devcontainer.json with the Claude Code feature and a non-root remoteUser
- [ ] 2. Persist ~/.claude and set CLAUDE_CONFIG_DIR so login survives rebuilds
- [ ] 3. No host secret mounts; pass short-lived tokens via env/Codespaces secrets
- [ ] 4. Egress firewall (default deny + allowlist) if running without prompts
- [ ] 5. Managed settings baked into the image if policy is needed
- [ ] 6. Run the escape tests below; then choose the permission mode
{
"name": "app-dev",
"build": { "dockerfile": "Dockerfile" },
"features": {
"ghcr.io/anthropics/devcontainer-features/claude-code:1.0": {}
},
"remoteUser": "node",
"runArgs": ["--cap-add=NET_ADMIN", "--cap-add=NET_RAW"],
"mounts": [
"source=claude-config-${devcontainerId},target=/home/node/.claude,type=volume"
],
"containerEnv": {
"CLAUDE_CONFIG_DIR": "/home/node/.claude",
"DISABLE_AUTOUPDATER": "1"
},
"postStartCommand": "sudo /usr/local/bin/init-firewall.sh"
}
CLAUDE_CONFIG_DIRmatters: auth and per-project trust live in.claude.json, which otherwise sits outside~/.claudeand is lost on rebuild.${devcontainerId}in the volume name isolates state per project.- The feature tag pins the install script, not the CLI. For a pinned CLI version, install
@anthropic-ai/[email protected]in the Dockerfile and keepDISABLE_AUTOUPDATER=1. NET_ADMIN/NET_RAWare needed only for an in-container firewall.- Bake org policy into the image:
COPY managed-settings.json /etc/claude-code/managed-settings.json. Anyone with repo write access can edit the Dockerfile, so for non-bypassable policy use server-managed settings or MDM. - If the browser login callback never reaches the container, paste the code shown in the browser at the terminal prompt.
Egress firewall (sketch)
Adapt from the reference init-firewall.sh in the anthropics/claude-code repository. The
shape:
#!/usr/bin/env bash
set -euo pipefail
ALLOWED=(api.anthropic.com registry.npmjs.org github.com api.github.com objects.githubusercontent.com)
iptables -F OUTPUT; ipset destroy allowed 2>/dev/null || true
ipset create allowed hash:net
for d in "${ALLOWED[@]}"; do
for ip in $(dig +short A "$d" | grep -E '^[0-9.]+$'); do ipset add -exist allowed "$ip"; done
done
HOST_NET=$(ip route | awk '/default/ {print $3}' | sed 's/\.[0-9]*$/.0\/24/') # editor port forwarding
iptables -A OUTPUT -o lo -j ACCEPT
iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
iptables -A OUTPUT -d "$HOST_NET" -j ACCEPT
iptables -A OUTPUT -m set --match-set allowed dst -j ACCEPT
iptables -P OUTPUT DROP
ip6tables -P OUTPUT DROP 2>/dev/null || true
curl -sS --max-time 5 https://example.com >/dev/null && { echo "FIREWALL OPEN"; exit 1; } || echo "egress locked"
- Get the authoritative list of Claude Code endpoints (inference, auth, optional telemetry) from the network access requirements page; the list above is illustrative.
- DNS answers rotate for CDNs; resolve at start and re-run the script if a host starts
failing. GitHub publishes ranges at
https://api.github.com/meta. - Allowing DNS (port 53) to anywhere permits DNS tunneling; for high-assurance setups restrict DNS to the container resolver.
- Add only registries and hosts the task needs. Every allowed domain is an exfiltration path for anything readable in the container.
Option C — disposable container or VM for unattended runs
docker run --rm -it --user 1000:1000 \
-v "$PWD":/workspace -w /workspace \
-e ANTHROPIC_API_KEY="$SCOPED_KEY" \
--memory 4g --pids-limit 512 \
my-claude-image claude --dangerously-skip-permissions -p "$TASK" --max-turns 40
- The CLI refuses
--dangerously-skip-permissionsas root; use a non-root user. - Mount a fresh clone, not your working copy, when the task is risky: bypass mode can rewrite or delete anything in the mounted workspace, and it appears on the host.
- Use a scoped, revocable API key or
claude setup-tokenoutput, never your personal cloud credentials. Rotate after runs on untrusted code. - For fewer prompts without full bypass, auto mode (a classifier reviews actions) is the
middle ground. Disable bypass org-wide with
"permissions": {"disableBypassPermissionsMode": "disable"}in managed settings.
Escape tests (run all, record results)
| Test | Expect |
|---|---|
curl -sS --max-time 5 https://example.com | Fails |
curl -sS https://api.anthropic.com (any HTTP status) | Connects |
ls ~/.ssh ~/.aws 2>&1 | Absent or not readable |
touch /etc/probe or outside workspace | Permission denied |
whoami | Non-root user |
env | grep -iE 'token|secret|key' | Only the intended scoped key |
docker ps (if socket not intended) | Not available |
Gotchas
- Mounting
/var/run/docker.sockinto the container gives root on the host. Never for agent containers. - Bind-mounting
~/.sshor~/.aws“just for git” defeats the boundary; use a repository-scoped deploy key or token. - Prompt injection from repo content is the main threat in bypass mode: the boundary is what limits the blast radius, not the model’s judgment.
- A firewall script that fails halfway leaves the container wide open;
set -euo pipefailand a final probe that exits non-zero when egress is open. - Hooks and MCP servers run outside the Bash sandbox (Option A); review them like code.
Output
**Boundary:** dev container (feature claude-code:1.0, user node), egress allowlist of 6 domains
**Mode:** --dangerously-skip-permissions inside container only; bypass disabled on hosts via managed settings
**Credentials:** scoped API key via Codespaces secret; no host mounts besides /workspace and config volume
**Escape tests:** 7/7 as expected (example.com blocked, api.anthropic.com reachable, ~/.ssh absent)
**Residual risk:** workspace is writable and mirrored to host; allowed registries could receive data About this skill
Written and maintained by the Claude Skills editors to the same editorial standard as every skill in the directory: a selective trigger, a real workflow, known pitfalls and a verification step. First published , last updated . MIT licensed.