Run AI coding agents inside a bubblewrap sandbox. One wrapper, any agent.
ai-bwrap launches an AI coding agent (Claude Code, opencode, Grok, or a plain shell) inside a bwrap namespace. The agent gets read-write access only to your current working directory; the rest of $HOME stays hidden. Just the config, cache, and state directories an agent actually needs are passed through.
Agents are declared as small shell functions, so the registry is extensible without touching the wrapper.
AI coding agents run shell commands, edit files, and fetch from the network on your behalf. Run directly, they can read anything you can: ~/.ssh, ~/.aws, browser profiles, every other project on disk. ai-bwrap confines them to the directory you're actually working in, while still passing through the toolchains and credentials they legitimately need (git, gh, Node/NVM, Cargo, …).
- Linux
bwrap(bubblewrap):sudo apt install bubblewrap # Debian/Ubuntu sudo pacman -S bubblewrap # Arch sudo dnf install bubblewrap # Fedora sudo zypper install bubblewrap # openSUSE
- The agent you want to run (
claude,opencode,grok, …) on your$PATH.
cp ai-bwrap ~/.local/bin/ai-bwrap
chmod +x ~/.local/bin/ai-bwrapai-bwrap <agent> [options] [-- agent args...]Built-in agents: claude, opencode, grok, bash (aliases: cc → claude, oc → opencode).
cd /path/to/your/project
ai-bwrap claude # Claude Code, confined to this directory
ai-bwrap oc --model anthropic/claude-sonnet-4-5 # opencode, flags passed through
ai-bwrap grok # Grok CLI
ai-bwrap bash # a shell inside the sandbox, to inspect itAnything after the agent name that ai-bwrap doesn't recognize is passed straight to the agent. Use -- to end option parsing explicitly.
| Option | Description |
|---|---|
--branch [dir] |
Copy the current directory to a throwaway branch and run there instead. Without [dir], a timestamped sibling directory is created. Your original tree is never touched. |
--git-rw |
Allow writes to git metadata (.git): commit, stash, switch branches. By default .git is mounted read-only. Combines with --overlay, see below. |
--overlay |
Run the agent on a private copy-on-write view of the working directory (kernel overlayfs). When the agent exits you see a summary of what changed and are asked whether to write it back to the real directory; answering no leaves your tree untouched and discards the changes. Only the working directory is overlayed, an agent's own config/cache dirs are bound directly and their changes always persist. Git metadata follows --git-rw: by default .git stays a read-only mount on top of the overlay, with --git-rw it is overlayed too, so commits are written back (or discarded) along with the worktree changes. Mutually exclusive with --branch. Needs bubblewrap 0.11+ and the attr package. |
--bind DIR |
Extra read-write bind mount (repeatable). |
--ro-bind DIR |
Extra read-only bind mount (repeatable). |
--dev-bind PATH |
Extra bind mount that keeps device access, for a device node or directory such as /dev/dri (repeatable). A device passed with --bind is visible in the sandbox but cannot be opened. |
--env KEY=VALUE |
Extra environment variable (repeatable). |
--no-net |
Run with networking disabled. |
--dry-run |
Print the bwrap command that would run, then exit. |
--list |
List available agents. |
-h, --help |
Show help. |
| Resource | Behavior |
|---|---|
Files outside $PWD |
Not visible (no bind) |
~/.ssh, ~/.aws, secrets, other projects |
Not mounted |
$HOME (other than passed-through dirs) |
Empty virtual dir |
Physical block devices (/dev/sda, …) |
Not present |
| Other host devices (GPUs, USB, …) | Not present, unless passed with --dev-bind |
DNS / /etc/resolv.conf |
Readable; where it symlinks into /run, that directory is bound read-only when networking is shared |
| Host processes (other PIDs) | Isolated PID namespace |
sudo / privilege escalation |
Blocked, no setuid in the namespace |
Common to every agent (read-only unless noted): $PWD (read-write), $PWD/.git (read-only, unless --git-rw), ~/.gitconfig, ~/.config/gh, $NVM_DIR, ~/.pyenv, ~/.local/bin, ~/.npm (rw), ~/.cargo (rw), /usr, /etc.
Each agent additionally passes through its own config/cache/state (e.g. ~/.claude, ~/.config/opencode, ~/.grok). See the agent functions in ai-bwrap for the exact list.
An agent is just a function named agent_<name> that declares its binds and the command to run. Define your own in ~/.config/ai-bwrap/config.sh (override with $AI_BWRAP_CONFIG), no need to edit the wrapper:
# ~/.config/ai-bwrap/config.sh
agent_aider() {
AGENT_BINDS+=( --bind-try "$HOME/.aider" "$HOME/.aider" )
EXEC_CMD=("$(require_cmd aider)")
}
# Extend the binds shared by all agents:
EXTRA_RO_BINDS+=( "$HOME/reference-repos" )
# Set extra environment variables shared by all agents:
EXTRA_ENV_VARS+=( 'FOO=bar' 'API_BASE=https://example.com' )
# Let agents use the GPU (here: AMD with ROCm). The device nodes need a device
# bind; the driver also reads /sys, and the toolchain lives in /opt/rocm:
EXTRA_DEV_BINDS+=( /dev/kfd /dev/dri )
EXTRA_RO_BINDS+=( /sys /opt/rocm )The same file can append to EXTRA_BINDS (read-write), EXTRA_RO_BINDS (read-only), EXTRA_DEV_BINDS (devices), and EXTRA_ENV_VARS (KEY=VALUE pairs). ai-bwrap --list shows custom agents alongside the built-ins.
bwrap uses Linux namespaces, the host kernel is shared. This is stronger isolation than running an agent directly, and lighter than a VM. For everyday development it's a practical tradeoff, but be aware:
- The host kernel is shared; a kernel-level exploit could escape the sandbox.
- Anything explicitly bind-mounted is reachable by the agent, so keep the passthrough list minimal.
- A device passed with
--dev-bindis talked to directly, driver and all: the agent can use it as freely as you can. Pass single devices rather than all of/dev. - Do not pass
/var/run/docker.sockthrough. Access to the Docker socket is equivalent to root on the host, so it removes the sandbox rather than extending it. If an agent needs Docker, run a filtering proxy in front of the socket and pointDOCKER_HOSTat the proxy. - Agents run with their own default permission behaviour;
ai-bwrapadds no flags of its own. If you consider the sandbox boundary sufficient and want to skip in-app prompts, pass the agent's own flag, e.g.ai-bwrap claude -- --dangerously-skip-permissions.
See SECURITY.md for the vulnerability reporting process.
Builds on opencode-bwrap and the bubblewrap project.
Contributions welcome, see CONTRIBUTING.md and the Code of Conduct.
- claude-chrome-multi-instance-warn, Claude Code plugin: warns once per session when a tool payload is about to launch another browser (Chrome/Playwright/Puppeteer/CDP) or fan work…
- claude-email-leak-notice, Claude Code plugin: warns once per session when a personal email address appears in a tool payload, so it does not end up in a commit or a pushed…
- agent.txtar, Portable text block for giving structured context to AI agents, embed anywhere, parse anywhere

