A containerized development environment for running Claude Code with bypassPermissions enabled. Built at Trail of Bits for security audit workflows.
Running Claude with bypassPermissions on your host machine is risky—it can execute any command without confirmation. This devcontainer provides filesystem isolation, so unrestricted Claude reaches only your project directory and a disposable container, not the rest of your host.
Designed for:
- Security audits: Review client code without exposing your host
- Untrusted repositories: Explore unknown codebases safely
- Experimental work: Let Claude modify code freely in isolation
- Multi-repo engagements: Work on multiple related repositories
-
Docker runtime (one of):
- Docker Desktop - ensure it's running
- OrbStack
- Colima:
brew install colima docker && colima start
-
For terminal workflows (one-time install):
npm install -g @devcontainers/cli git clone https://github.com/trailofbits/claude-code-devcontainer ~/.claude-devcontainer ~/.claude-devcontainer/install.sh self-install
Optimizing Colima for Apple Silicon
Colima's defaults (QEMU + sshfs) are conservative. For better performance:
# Stop and delete current VM (removes containers/images)
colima stop && colima delete
# Start with optimized settings
colima start \
--cpu 4 \
--memory 8 \
--disk 100 \
--vm-type vz \
--vz-rosetta \
--mount-type virtiofsAdjust --cpu and --memory based on your Mac (e.g., 6/16 for Pro, 8/32 for Max).
| Option | Benefit |
|---|---|
--vm-type vz |
Apple Virtualization.framework (faster than QEMU) |
--mount-type virtiofs |
5-10x faster file I/O than sshfs |
--vz-rosetta |
Run x86 containers via Rosetta |
Verify with colima status - should show "macOS Virtualization.Framework" and "virtiofs".
Choose the pattern that fits your workflow:
Each project gets its own container with independent volumes. Best for one-off reviews or when you need isolation between projects.
Terminal:
git clone <untrusted-repo>
cd untrusted-repo
devc . # Installs template + starts container
devc shell # Opens shell in containerVS Code / Cursor:
Not recommended for untrusted code. Container code can execute commands on your host through this path, by design. See Threat Model.
-
Install the Dev Containers extension:
- VS Code:
ms-vscode-remote.remote-containers - Cursor:
anysphere.remote-containers
- VS Code:
-
Set up the devcontainer (choose one):
# Option A: Use devc (recommended) devc . # Option B: Clone manually git clone https://github.com/trailofbits/claude-code-devcontainer .devcontainer/
-
Open your project folder in VS Code, then:
- Press
Cmd+Shift+P(Mac) orCtrl+Shift+P(Windows/Linux) - Type "Reopen in Container" and select Dev Containers: Reopen in Container
- Press
A parent directory contains the devcontainer config, and you clone multiple repos inside. Shared volumes across all repos. Best for client engagements, related repositories, or ongoing work.
# Create workspace for a client engagement
mkdir -p ~/sandbox/client-name
cd ~/sandbox/client-name
devc . # Install template + start container
devc shell # Opens shell in container
# Inside container:
git clone <client-repo-1>
git clone <client-repo-2>
cd client-repo-1
claude # Ready to workFor headless servers or to skip the interactive login wizard:
claude setup-token # run on host, one-time
export CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-...
devc rebuild # rebuilds with tokenThe token is forwarded into the container. On each container creation, post_install.py runs a one-shot auth handshake so claude starts without the login wizard.
This works around Claude Code's interactive onboarding wizard always showing in containers, even with valid credentials (#8938).
If you don't set a token, the interactive login flow works as before.
devc . Install template + start container in current directory
devc up Start the devcontainer
devc rebuild Rebuild container (preserves persistent volumes)
devc destroy [-f] Remove container, volumes, and image for current project
devc down Stop the container
devc shell Open zsh shell in container
devc exec CMD Execute command inside the container
devc upgrade Upgrade Claude Code in the container
devc mount SRC DST Add a bind mount (host → container)
devc sync [NAME] Sync Claude Code sessions from devcontainers to host
devc cp SRC DST Copy a path from the container to the host
devc template DIR Copy devcontainer files to directory
devc self-install Install devc to ~/.local/bin
devc update Update devc to the latest version
Note: Use
devc destroyto clean up a project's Docker resources. Removing containers manually (e.g.,docker rm) will leave orphaned volumes and images behind thatdevc destroywon't be able to find.
Claude Code's /insights command analyzes your session history, but it only reads from ~/.claude/projects/ on the host. Sessions inside devcontainer volumes are invisible to it.
devc sync copies session logs from all devcontainers (running and stopped) to the host so /insights can include them:
devc sync # Sync all devcontainers
devc sync crypto # Filter by project name (substring match)Devcontainers are auto-discovered via Docker labels — no need to know container names or IDs. The sync is incremental, so it's safe to run repeatedly.
Security note: this copies container-authored data onto your host, so it prompts first (
--trustedskips it). Only*.jsonllogs are copied, always under a-devcontainer-<project>key, so a container cannot plant files elsewhere in~/.claude/projects/. The transcripts are still container-authored text that a later host session will read.
Drag files from your host into the VS Code Explorer panel — they are copied into /workspace/ automatically. No configuration needed.
To make a host directory available inside the container:
devc mount ~/drop /drop # Read-write
devc mount ~/secrets /secrets --readonlyThis adds a bind mount to devcontainer.json and recreates the container. Existing mounts are preserved across devc template updates.
Tip: A shared "drop folder" is useful for passing files in without mounting your entire home directory.
Security note: Avoid mounting large host directories (e.g.,
$HOME). Every mounted path is writable from inside the container unless--readonlyis specified, which undermines the filesystem isolation this project provides.
By default, containers have full outbound network access. For stricter security, use iptables to restrict network access.
- Reviewing code that may contain malicious dependencies
- Auditing software with telemetry or phone-home behavior
- Maximum isolation for highly sensitive reviews
Run this inside the container (devc shell). The allowlist lives in an ipset that the
iptables rule references by name, so refreshing it does not mean re-adding rules.
# 1. Loopback, plus DNS to whatever resolver the container was given.
# Without this the final DROP blocks name resolution and nothing works.
sudo iptables -A OUTPUT -o lo -j ACCEPT
for ns in $(awk '/^nameserver/{print $2}' /etc/resolv.conf); do
sudo iptables -A OUTPUT -p udp -d "$ns" --dport 53 -j ACCEPT
sudo iptables -A OUTPUT -p tcp -d "$ns" --dport 53 -j ACCEPT
done
# 2. Resolve the allowlist into an ipset.
sudo ipset create allowed-egress hash:ip -exist
for host in api.anthropic.com github.com raw.githubusercontent.com \
registry.npmjs.org pypi.org files.pythonhosted.org; do
for ip in $(getent ahostsv4 "$host" | awk '{print $1}' | sort -u); do
sudo ipset add allowed-egress "$ip" -exist
done
done
# 3. Allow the set, drop everything else.
sudo iptables -A OUTPUT -m set --match-set allowed-egress dst -j ACCEPT
sudo iptables -A OUTPUT -j DROP- Blocks package managers unless you allowlist registries
- May break tools that require network access
- DNS is permitted (DNS remains an exfiltration channel)
- The allowlist is per-IP, so any other site behind the same CDN address is also reachable
- IPv6 is not filtered. If your Docker network has an IPv6 default route, mirror the
rules with
ip6tablesand anipset ... family inet6 - Rules are lost on container restart; re-apply them per session
Protects against:
- Claude with
bypassPermissionsrunning wild during a session. - Direct access to your SSH key material and other credentials
- Unrestricted, direct access to the whole filesystem
- Cross-engagement leakage
Does not protect against:
- Container escape. A container is containment, not a strong security boundary. Escape should be hard, not impossible.
- Deferred escape. Container-planted code can get executed on the host, when the user performs some action on the host. Planting files under shared
.gitfolder is an example escape path. - VS Code "Reopen in Container". The command runs an extension host inside the container wired to your editor over RPC, and container code can drive host-only editor commands (
terminal.newLocalthensendSequence) to run shell commands on your host. This is Microsoft's design, not a bug here (how it works). - Network rules overwrite. Container has
NET_ADMINand passwordless sudo, its user can change the iptables rules dynamically. - Exfiltration of in-container credentials. Claude, GitHub, and other tokens provided to container are simply accessible inside it.
Also not isolated: forwarded SSH agent (container code can authenticate as you; keys stay on the host), ~/.gitconfig (read-only). The Docker socket is not mounted.
| Component | Details |
|---|---|
| Base | Ubuntu 24.04, Node.js 24, Python 3.13 + uv, zsh |
| User | vscode (passwordless sudo), working dir /workspace |
| Tools | rg, fd, tmux, fzf, delta, iptables, ipset |
| Volumes (survive rebuilds) | Command history (/commandhistory), Claude config (~/.claude), GitHub CLI auth (~/.config/gh) |
| Host mounts | ~/.gitconfig, .devcontainer/, .git/config, .git/hooks/ (all read-only) |
| Auto-configured | bypassPermissions mode (via post_install.py), skills from anthropics/skills + trailofbits/skills + trailofbits/skills-curated, git-delta |
Volumes are stored outside the container, so your shell history, Claude settings, and gh login persist even after devc rebuild. Host ~/.gitconfig is mounted read-only for git identity.
The container ships common development tooling so you can do all your work inside it, not just run Claude. The intended workflow is: clone a repository, start the container, and stay in it. If you need extra runtimes, add them to the Dockerfile for repeat use or install them ad-hoc with devc exec.
npm install -g @devcontainers/cli- Check Docker is running
- Try rebuilding:
devc rebuild - Check logs:
docker logs $(docker ps -lq)
The gh volume may need ownership fix:
sudo chown -R $(id -u):$(id -g) ~/.config/ghPython is managed via uv:
uv run script.py # Run a script
uv add package # Add project dependency
uv run --with requests py.py # Ad-hoc dependencyBuild the image manually:
devcontainer build --workspace-folder .Test the container:
devcontainer up --workspace-folder .
devcontainer exec --workspace-folder . zsh