isolate
August 18, 2026 · View on GitHub
A tiny bash wrapper that runs AI coding agents (opencode, Claude, Cursor,
Copilot, etc.) under filesystem isolation using
bubblewrap.
Everything on your filesystem stays readable to the agent. Only a handful
of paths — the current working directory, /tmp, some device nodes, and the
agent's own config/auth directories — are writable.
This is deliberately a thin convenience script, not a hardened sandbox.
Install
# Fedora / RHEL
dnf install bubblewrap
# Debian / Ubuntu
apt install bubblewrap
# Arch
pacman -S bubblewrap
# drop `isolate` somewhere on your $PATH
macOS
isolate relies on bubblewrap, which uses Linux kernel namespaces and does not exist on macOS.
For macOS, Agent Safehouse provides similar filesystem isolation for AI coding agents using the built-in sandbox-exec / Seatbelt framework. It is a single bash script — no dependencies beyond macOS itself.
Usage
isolate opencode [agent-args...]
isolate claude
isolate cursor
isolate copilot
Run isolate with no arguments for the full list of supported agents and flags.
Set ISOLATE_DEBUG=1 (or --debug) to print the bwrap command instead of executing it.
Flags
| Flag | Effect |
|---|---|
--allow-docker | Expose /run/docker.sock inside the sandbox |
--allow-podman | Expose /run/podman/podman.sock inside the sandbox |
--no-network | Isolated network namespace (loopback only) |
--debug | Print bwrap command instead of executing |
Environment variables:
| Variable | Effect |
|---|---|
ISOLATE_DEBUG | Same as --debug |
ISOLATE_WRITABLE | Space-separated extra writable paths |
ISOLATE_MASK_ALLOW | Space-separated dirs to keep visible from the default mask list |
ISOLATE_KUBE_CONTEXTS | Space-separated kubectl context names to proxy (default: current context) |
ISOLATE_AWS_PROFILES | Space-separated AWS profile names to expose as read-only via a chained AssumeRole. Backed by an outside-sandbox credential minter holding the user's SSO session; the sandbox sees no static AWS credentials. |
ISOLATE_AWS_SOURCE_PROFILE | SSO profile used as the root of the chained AssumeRole (required when ISOLATE_AWS_PROFILES is set). |
ISOLATE_AWS_SHARED_SERVICES_ACCOUNT | AWS account ID containing the intermediate source roles. |
ISOLATE_AWS_SOURCE_ROLE_PATTERN | Python .format() pattern for the intermediate role name, e.g. my-{name}-plan. |
ISOLATE_AWS_TARGET_ROLE | Name of the read-only target role assumed in each account. |
What it does
- Creates a new mount namespace with the entire host filesystem mounted read-only.
- Bind-mounts writable directories over the read-only base:
- the current directory (
pwd) /tmp,/var/tmp- your language-runtime caches (
.cache,.local,.npm,.bun) - the agent's own config and auth directories
(
.mcp_auth,.docker,.claude,.claude.json,.cursor,.config/cursor,.copilot, ...)
- the current directory (
/runis replaced with an empty tmpfs, masking the Docker, podman, D-Bus, and libvirt sockets. Without a socket to connect to, those runtimes are genuinely inaccessible. A bash function also gives a friendly error if the agent typesdocker,podman, orvirsh.~/.aws,~/.kube, and~/.sshare masked with tmpfs. WithISOLATE_KUBE_CONTEXTSand/orISOLATE_AWS_PROFILESset,isolateruns short-lived proxies or credential minters outside the sandbox and writes synthetic configs in/tmpso the agent gets read-only access without ever seeing real credentials. Kubernetes useskubectl proxywith POST/PUT/PATCH/ DELETE rejected at the HTTP layer; AWS uses a chained STS AssumeRole into a read-only role in each account, with credentials refreshed on demand through a Unix socket. Inside the sandbox these denials surface as ordinary errors:kubectlwrites returnerror: <method> against <path> is forbidden, AWS write API calls returnAn error occurred (AccessDenied) when calling the <Operation> operation: .... Both are the sandbox speaking, not a config bug.- SSH agent socket and DNS resolver are preserved through
/runfor git push/pull. - Mirrors a new
/dev(device nodes) and/proc(isolated procfs). - Creates isolated PID, IPC, and session namespaces (
--unshare-pid,--unshare-ipc,--new-session). - Strips a few environment variables that could leak tmux/systemd identity
or enable a sandbox escape (
DBUS_SESSION_BUS_ADDRESS,XDG_RUNTIME_DIR,TMUX, ...) and passes the rest through. - Shares the host network by default (
--no-networkfor loopback-only).
Weaknesses — read this before you trust it
This is a helper, not a security boundary. It makes a mistake or a prompt-injection attack less damaging, not impossible.
- Everything is readable. The agent can read any file you can, including
~/.ssh/id_*,~/.aws/credentials,.envfiles, browser cookie stores, and private keys. Do not run agents on a machine where readable files contain secrets you wouldn't hand to the model. - Network is shared by default. Because the agent can read your whole
filesystem and talk to the network, a compromised or prompt-injected
agent can exfiltrate any readable data. There is no egress filtering.
Use
--no-networkif egress matters. - Credential directories are writable. The agent's own auth directories
(OAuth tokens,
.claude.json,auth.json, etc.) are both readable and writable, so a malicious agent could rewrite or steal those tokens. - Environment passthrough is a denylist. Any environment variable not in
the short strip-list (e.g.
AWS_*,GITHUB_TOKEN,ANTHROPIC_API_KEY) is passed into the sandbox verbatim. - Not bulletproof. Kernel namespaces restrict filesystem and process and process access; they do not filter syscalls or provide a complete security boundary. A determined agent with code execution can look for ways to escape. Treat this as defense-in-depth, not a jail.
If you need real isolation, run the agent in a dedicated VM, container, or
throwaway account. isolate is the convenient middle ground — better than
unrestricted access, far weaker than a real sandbox.
Keyring-backed tools (e.g. gh)
Masking /run and stripping DBUS_SESSION_BUS_ADDRESS also hides the D-Bus
session bus, so anything that keeps credentials in the system keyring
(libsecret / secret-service) can't reach them inside the sandbox. GitHub CLI is
the usual casualty: gh reports "The token in default is invalid" because its
OAuth token lives in the keyring, not on disk.
The fix is to move the token to gh's file storage, which stays readable inside
the sandbox — no D-Bus required. Run once, outside the sandbox:
gh auth token -h github.com \
| gh auth login -h github.com --git-protocol ssh --with-token --insecure-storage
The token then lives in ~/.config/gh/hosts.yml (plaintext, mode 600). The same
approach applies to other keyring-backed CLIs: use a file/env credential instead.
License
Released into the public domain (CC0). See LICENSE.