Your real dev environment, sandboxed per project. Run Claude Code, Codex, Copilot, and other AI coding agents safely - without giving up your shell, your mise-managed tools, or your editor configs.
Docker and VMs isolate by replacing your dev environment. Fresh shell with no aliases. No mise, no editor, no prompt. Reinstall every tool inside the container, fight file watchers across the VM boundary, and wait 10-30 seconds for cold starts. So most people skip isolation entirely and let agents run on the host - with full access to ~/.ssh, cloud credentials, .env secrets, and every other project on disk.
devsandbox closes that gap. It wraps any command in a sandbox scoped to your current working directory and brings the rest of your real environment with it:
- Your shell, aliases, history. Detected from
$SHELLand bound read-only. mise-managed tools. Go, Node, Python, kubectl, whatever - already there, nomise installtwice.- Editor + LSP, prompt, multiplexer. nvim, helix, starship, tmux, fish, zsh - all preserved.
- Sub-second startup. bubblewrap on Linux shares the host kernel; native file watching works.
The isolation boundary is still real. Inside the sandbox, the agent sees the project directory, your tools, and the system paths and configs they need to run (mount layout) - not the rest of your home directory. SSH keys, cloud credentials (~/.aws, ~/.azure, ~/.gcloud), .env files in the project tree (scan limits), sibling projects, and parent directories are invisible. .git is read-only by default. An optional MITM proxy logs and filters the sandbox's HTTP/HTTPS traffic, with enforcement strength that varies by backend (per-backend behavior).
devsandbox runs without mise, but the recommended install method uses it, and it is what makes your host toolchain (Go, Node, Python, kubectl) available inside the sandbox. Without it, everything else still works - you just bring your own tools.
Linux:
curl https://mise.jdx.dev/install.sh | shAfter installing, activate mise in your shell (setup guide):
# bash
echo 'eval "$(~/.local/bin/mise activate bash)"' >> ~/.bashrc
# zsh
echo 'eval "$(~/.local/bin/mise activate zsh)"' >> ~/.zshrc
# fish
echo '~/.local/bin/mise activate fish | source' >> ~/.config/fish/config.fishRequired on Linux: your kernel must support unprivileged user namespaces. Verify with:
unshare --user true
# Should succeed silently. If it fails, see Limitations.macOS:
brew install miseA Docker runtime is also required (ensure it is running before using devsandbox):
- OrbStack - recommended for Apple Silicon (fastest startup, lowest resource usage)
- Docker Desktop - most widely tested
- Colima - free and open-source
Install:
mise use -g github:zekker6/devsandboxHomebrew is not currently available. For direct binary download, see Installation Details.
Sandbox your AI agent:
# 1. cd into your project
cd ~/projects/my-app
# 2. Run Claude Code in the sandbox (scoped to ~/projects/my-app)
devsandbox claude --dangerously-skip-permissions
# 3. Verify what's protected
devsandbox --infodevsandbox sandboxes the current working directory - cd into your project first, then run devsandbox. Everything after devsandbox is passed to the sandboxed command. --dangerously-skip-permissions is a Claude Code flag that skips permission prompts - safe inside the sandbox because devsandbox provides the security boundary.
Works with: Claude Code, Codex CLI, GitHub Copilot, Pi, OpenCode, aider, and any other CLI-based development tool. The first five also get shell wrappers and session persistence across sandbox restarts.
That's it. No config files needed. On Linux the default sandbox needs no system packages - bwrap and pasta are embedded. Proxy mode (--proxy) is the one exception: it needs iproute2 and nft or iptables on the host for its egress lockdown, and aborts rather than starting with open egress. On macOS, a Docker runtime is required (see Installation Details).
Run devsandbox doctor to verify your setup.
macOS: devsandbox runs your code inside a lightweight Linux container (Debian slim) via Docker. Your project files are mounted into the container, so edits sync bidirectionally. Ensure your Docker runtime is running before using devsandbox. The first start downloads a base Docker image (~200MB); subsequent starts reuse Docker layer caching and complete in 1-2 seconds.
Sometimes you want to run a sandboxed tool in an empty working directory - try a library, give an AI agent a clean slate, or quickly build something unrelated to your current project. devsandbox scratchpad gives you a managed, persistent scratch workspace without having to mkdir under /tmp by hand.
# Interactive shell in the default scratchpad
devsandbox scratchpad
# Named scratchpad "experiments", interactive shell
devsandbox scratchpad experiments
# Named scratchpad with a command
devsandbox scratchpad experiments claude --dangerously-skip-permissions
# Command in the default scratchpad (name it explicitly)
devsandbox scratchpad default bun init
# Ephemeral run - wipe state on exit
devsandbox scratchpad --rm experiments bun initScratchpad working directories live at ~/.local/share/devsandbox-scratchpads/scratchpad-<name>/ and persist between runs. The sandbox state (home overlay, tool caches) lives in the usual ~/.local/share/devsandbox/ tree alongside your project sandboxes.
Local .devsandbox.toml files are never loaded inside a scratchpad - the baseline is always global config only, so scratchpads stay uncontaminated by project-specific overrides.
# List all scratchpads
devsandbox scratchpad list
devsandbox scratchpad list --json
# Remove one (cleans working dir + sandbox state)
devsandbox scratchpad rm experiments
# Wipe the working dir but keep the sandbox home overlay
devsandbox scratchpad rm experiments --keep-state
# Remove everything
devsandbox scratchpad rm --all --forceDX is the headline; isolation is the floor. The defaults are tuned so an agent inside a fresh sandbox can do its job and nothing more - no flags required.
CAN: Read/write your project files, use your mise-managed tools, inherit your shell and editor configs, run build commands, install dependencies, make API calls (logged in proxy mode).
CANNOT: Read SSH keys, access cloud credentials (AWS/Azure/GCloud), read .env secrets (masked up to 3 directory levels below the project root, skipping node_modules, .git, vendor, .venv), see other projects, push to git (by default), or modify your system.
| Resource | Default Access |
|---|---|
| Project directory | Read/Write |
.env / .env.* files |
Hidden (masked with /dev/null); scanned up to 3 directory levels below the project root, skipping node_modules, .git, vendor, .venv |
~/.ssh |
Not mounted |
~/.aws, ~/.azure, ~/.gcloud |
Not mounted |
~/.gitconfig |
Sanitized (user.name/email only) |
.git directory |
Read-only (no commits, no credentials) |
| mise-managed tools | Read-only |
| Network (default) | Full access |
| Network (proxy mode) | Isolated and logged; enforcement strength varies by backend (see per-backend behavior) |
| Outgoing secrets (proxy + redaction) | Blocked or redacted in requests the proxy sees (see redaction coverage) |
| Host memory, CPU, process count | Unlimited by default on bwrap and docker; krun defaults to 4g memory and 2 CPUs. Cap them with [sandbox.resources] |
Everything is configurable. See Configuration for details.
- Your real dev env, inside the sandbox - mise-managed tools, shell configs, editor setups (nvim, starship, tmux) auto-detected and bound, no Dockerfile required. On Linux, the
dockerandkrunbackends share the host's mise installs too, so your toolchain resolves in-guest without reinstalling (details) - Sub-second startup - bubblewrap namespaces on Linux share the host kernel; native file watching works. Docker layer caching keeps macOS restarts at 1-2s
- Per-project isolation - each project gets its own sandbox home, caches, and logs
- Zero-config security baseline - SSH keys, cloud credentials,
.envfiles (scan limits), and git credentials blocked by default - MITM proxy - optional traffic inspection with log viewing, filtering, and export
- HTTP filtering - whitelist/blacklist domains, or interactively approve requests one at a time
- Content redaction - scan outgoing requests for secrets, block or replace them before they leave your machine (coverage)
- Resource limits - optional memory, CPU and process caps that apply to every backend (
[sandbox.resources]); a limit that cannot be enforced aborts the launch instead of running unlimited - Agent shell wrappers - opt-in shell functions so
claudemeansdevsandbox claude, withclaude-no-dsandcommand claudeas escape hatches (details) - herdr agent session restore - a sandboxed agent reports its native session through the filtered herdr proxy, and a restored pane resumes it back inside the sandbox (details)
- Agent sessions survive the sandbox -
claude,codex,copilot,piandopencodekeep their native session stores on the host, so--resume/--continuefinds conversations started in an earlier run (details) - Dev tool integrations -
rtkkeeps its filters and tracking database (details),revdiffopens review overlays in kitty or herdr, and the Docker socket is proxied read-only - Git modes - readonly (default), readwrite (with SSH/GPG), or disabled
- Desktop notifications - sandboxed apps can send notifications to the host via XDG Desktop Portal (Linux)
Linux: Uses bubblewrap to create namespace-based isolation. No root privileges, no Docker, no system packages required for the default sandbox - bwrap and pasta binaries are embedded. Startup is sub-second. Proxy mode (--proxy) additionally needs iproute2 and nft or iptables on the host for its egress lockdown; see proxy mode.
macOS: Uses Docker containers with volume mounts that mirror the bwrap behavior. Named volumes provide near-native filesystem performance. Containers are cached for 1-2 second restarts.
All backends automatically detect your shell, tools, and editor configs and make them available read-only inside the sandbox. On a Linux host, the container backends additionally share the host's mise installs, so your toolchain resolves in-guest without a reinstall; a macOS host shares nothing, so tools are installed inside the sandbox there (details).
Untrusted code (experimental): bwrap and Docker share the host kernel, so a kernel-level exploit escapes both. The opt-in krun backend (--isolation krun) runs the same sandbox image inside a libkrun microVM via podman --runtime krun, giving the workload its own guest kernel behind a hardware virtualization boundary (KVM on Linux, HVF on macOS). Requires podman, a libkrun-enabled crun, and /dev/kvm on Linux or Apple Silicon on macOS (plus nft or iptables for proxy mode on Linux, usually already present). See Isolation Backend.
# Interactive sandbox shell
devsandbox
# Run any command in the sandbox
devsandbox npm install
devsandbox go test ./...
devsandbox cargo build
# AI assistant with traffic monitoring
devsandbox --proxy claude --dangerously-skip-permissions
# View what the AI accessed
devsandbox logs proxy --last 50
# Follow traffic in real-time (in a second terminal)
devsandbox logs proxy -f
# Whitelist-only network access
devsandbox --proxy --filter-default=block \
--allow-domain="*.github.com" \
--allow-domain="api.anthropic.com"
# Choose isolation backend explicitly
devsandbox --isolation=docker npm install
# Ephemeral sandbox (removed after exit)
devsandbox --rm
# Make supported agents sandboxed by default: `claude` becomes `devsandbox claude`.
# Add to ~/.bashrc (fish and zsh forms: devsandbox agent-wrappers --help)
if [ -z "${DEVSANDBOX:-}" ]; then eval "$(devsandbox agent-wrappers activate bash)"; fiBy default, .git is mounted read-only - you can view history, diff, and status, but commits are blocked and no credentials are exposed.
| Mode | .git |
Commits | Credentials |
|---|---|---|---|
readonly |
read-only | blocked | none (default) |
readwrite |
read-write | allowed | SSH, GPG, credentials |
disabled |
read-write | allowed | none |
# ~/.config/devsandbox/config.toml
[tools.git]
mode = "readwrite" # for trusted projects that need push/signdevsandbox --worktree creates (or reuses) a git worktree and enters the sandbox rooted there. Agent edits land on a dedicated branch without touching your main checkout.
| Flag | Behavior |
|---|---|
--worktree |
Auto-generate devsandbox/<session-or-timestamp> off HEAD. |
--worktree=<branch> |
Reuse the branch if it exists; otherwise create it off --worktree-base. |
--worktree-base=<ref> |
Base ref when creating a new branch. Defaults to HEAD. Ignored if the branch already exists. |
Interaction with --git-mode:
| Combination | Effect |
|---|---|
--worktree + --git-mode=readonly (default) |
git status/log/diff work; commits fail. |
--worktree + --git-mode=readwrite |
Commits land on the worktree's branch only. Main checkout untouched. |
--worktree + --git-mode=disabled |
Rejected at flag parse time. |
--worktree + --rm |
Worktree removed on exit (git worktree remove --force + prune). |
--worktree outside a git repo |
Rejected before sandbox spin-up. |
Worktrees live under the per-project sandbox state dir:
~/.local/share/devsandbox/<project-slug>/worktrees/<sanitized-branch>/
The slug is derived from the main repo root so worktrees of the same repo share sandbox state (overlays, logs). Branch names with slashes are stored as dashes in the filesystem leaf; the git branch name is preserved verbatim.
Known limitations:
- Submodule init inside a readonly sandbox fails - not worked around.
- If git already has the canonical path registered to a different branch, invocation fails with git's own error plus a hint; run
git worktree listto investigate. - A stale directory at the canonical path (git has no record) causes
devsandboxto refuse to clobber - remove it manually.
Route HTTP/HTTPS traffic through a local MITM proxy. See every API call your AI agent makes in real-time, block suspicious domains, or interactively approve each request. How strongly the routing is enforced depends on the backend - bwrap and krun both fail closed behind a deny-by-default egress firewall (requires nft or iptables), Docker is env-var routing only; see per-backend behavior.
# Enable proxy
devsandbox --proxy
# View logs
devsandbox logs proxy --stats # Summary statistics
devsandbox logs proxy --errors # Failed requests only
devsandbox logs proxy --json # JSON export for scripting
# Interactive request approval
devsandbox --proxy --filter-default=ask
# Then in another terminal:
devsandbox proxy monitorOn Linux, proxy mode uses pasta for network namespace isolation (embedded, no install needed). On macOS, it uses per-session Docker networks.
See Proxy Mode docs for filtering rules, log formats, and remote logging setup. Audit-grade structured logging - per-session metadata, lifecycle events, and security events forwarded to syslog/OTLP - is documented under Audit Logging.
Linux:
Requirements:
- Linux kernel with unprivileged user namespaces enabled (verify:
unshare --user trueshould succeed silently) - No system packages required (bwrap and pasta binaries are embedded)
- Proxy mode only:
iproute2andnft(oriptables), with thenf_tables/nf_conntrackkernel modules loadable - a--proxylaunch aborts without them
# Option 1: mise
mise use -g github:zekker6/devsandbox
# Option 2: Download binary
curl -L https://github.com/zekker6/devsandbox/releases/latest/download/devsandbox_Linux_x86_64.tar.gz | tar xz
sudo mv devsandbox /usr/local/bin/To use system-installed binaries instead of embedded ones, set use_embedded = false in configuration.
Optional system packages (fallback if embedded extraction fails). Note: the passt package provides the pasta binary used for network namespace isolation.
# Arch Linux
sudo pacman -S bubblewrap passt
# Debian/Ubuntu
sudo apt install bubblewrap passt
# Fedora
sudo dnf install bubblewrap passtmacOS: Requires a Docker runtime - see Prerequisites for options.
Build from source:
# Requires: Go 1.26+ and Task (https://taskfile.dev/)
# Or use mise to install dependencies: mise install
task buildThe project ships a small documentation site (Zensical + a hand-written landing page) that deploys to GitHub Pages from main. To work on it locally:
# Live-reload dev servers - landing on :8001, docs on :8000 (loopback only)
task site:dev
# Production-style assembly into ./public/
task site:build
# Clean build artifacts
task site:cleanzensical.toml at the repo root configures the docs site; landing source lives under site/landing/. The CI workflow .github/workflows/pages.yml runs the same task site:build and uploads ./public/ to GitHub Pages.
One-time setup (must be done in the GitHub UI, cannot be done from the workflow): Repository → Settings → Pages → Source = "GitHub Actions". After the first successful workflow run, the site is reachable at https://zekker6.github.io/devsandbox/.
devsandbox # Interactive sandbox shell
devsandbox <command> # Run command in sandbox
devsandbox --proxy # Enable proxy mode
devsandbox --rm # Ephemeral sandbox
devsandbox --info # Show sandbox configuration
devsandbox doctor # Check installation
devsandbox scratchpad [name] # Sandbox in a clean scratch workspace (alias: sp)
devsandbox scratchpad list # List scratchpads
devsandbox config init # Generate config file
devsandbox config show # Print the resolved configuration
devsandbox config path # Print the config file location
devsandbox sandboxes list # List all sandboxes
devsandbox sandboxes prune # Remove stale sandboxes
devsandbox sessions # List running sandbox sessions
devsandbox forward 3000 # Forward a host port into a running sandbox
devsandbox logs proxy # View proxy logs
devsandbox logs proxy -f # Follow logs in real-time
devsandbox tools list # List available tools
devsandbox tools check # Verify tool setup
devsandbox trust add <path> # Trust a local .devsandbox.toml
devsandbox overlay migrate # Promote overlay contents to the host path
devsandbox agent-wrappers activate # Print wrappers to eval from your startup file
devsandbox run-agent claude ... # Wrapper entrypoint: re-enter the sandbox
devsandbox image build # Build Docker image (macOS)| Page | Contents |
|---|---|
| Sandboxing | Isolation backends, security model, filesystem layout, overlay mounts, custom mounts, Docker backend details |
| Proxy Mode | Traffic inspection, log viewing/filtering/export, HTTP filtering, ask mode, content redaction, credential injection, remote logging |
| Tools | mise integration, shell/editor/prompt setup, AI assistant configs and shell wrappers, rtk, Git modes, Docker socket proxy, kitty/herdr/zellij terminal integration, XDG desktop portal |
| Configuration | Config file reference, per-project configs, conditional includes, port forwarding, overlay settings, resource limits |
| Use Cases | Shell aliases, autocompletion, development workflows, security monitoring scripts |
Linux (bwrap):
- Requires unprivileged user namespaces (see Troubleshooting for distro-specific guidance)
- SELinux or AppArmor may restrict namespace operations (see Security Modules)
- MITM proxy may break tools with certificate pinning
- Proxy mode needs
nftoriptableswithnf_tables/nf_conntrackloaded for its egress lockdown; a launch aborts rather than run with open egress. Proxy-mode sandboxes are IPv4-only (pasta runs with-4) - GUI applications are not supported (no display server forwarding), but desktop notifications work via XDG Portal
[sandbox.resources]limits need cgroup v2 and a systemd user manager with thememory/cpu/pidscontrollers delegated touser@<uid>.service; a limit that cannot be enforced aborts the launch. Nothing about systemd is required when no limits are configured
macOS (Docker):
- Requires a running Docker daemon
- Project directory access goes through macOS virtualization (VirtioFS/gRPC-FUSE), which may be slower for I/O-heavy operations. Sandbox-internal operations (npm install, Go builds) use named Docker volumes with near-native speed.
- File watching (hot reload) may require polling mode. See File Watching Limitations for workarounds.
- Network isolation uses HTTP_PROXY instead of pasta
krun (microVM, experimental):
- Proxy-mode egress lockdown is applied host-side in the VMM's pasta network namespace and needs
nftoriptableson the host devsandbox forwardis best-effort - the session is registered, but reaching a guest listener is not yet validated- macOS is not yet validated and requires Apple Silicon; proxy mode is refused on macOS because the egress lockdown is Linux-only (fails closed rather than running with open egress)
- IPv6 is disabled in the guest under proxy mode (pasta runs with
-4) pidsfrom[sandbox.resources]is not enforceable - the container is the libkrun VMM process and the guest kernel owns its own PID space, so krun skips the flag and warns;memoryandcpusapply normally (defaulting to4g/2)- Every launch boots a fresh microVM - no
keep_containerreuse, and no online boot-time install of the project's mise tools (see krun backend)
Both:
- Docker socket access is read-only (no container creation/deletion) - see Tools docs
- No nested Docker (cannot run Docker inside the sandbox)
MIT