Installs
Work on Linux as they are: the registries and github.com are in the default list. On macOS add one, --allow-host registry.npmjs.org, or open everything with --network.
ai-jail is one program with no daemon and no root. You install it, put its name in front of the command you already type, and the agent starts inside a sandbox.
For the person at the keyboard.
Choose the way you already install command line tools. Every method gives you the same single ai-jail program.
brew tap akitaonrails/tap && brew install ai-jailWorks on macOS and on Linux.
yay -S ai-jail-binyay -S ai-jailThe first package is a prebuilt binary for Linux x86_64. The second builds from source.
cargo install --locked ai-jailNeeds a Rust toolchain. --locked builds with the dependency versions the release was tested with.
nix run github:akitaonrails/ai-jail -- claudenix profile install github:akitaonrails/ai-jailThe first line runs an agent without installing anything. The second installs for your user. The flake points ai-jail at its own copy of bubblewrap.
ai-jail-linux-x86_64.tar.gzai-jail-macos-aarch64.tar.gzThe archives are signed, with checksums next to them. Unpack one and put ai-jail somewhere on your PATH. How to check the signature is on the Security page.
cargo build --release --lockedinstall -Dm755 target/release/ai-jail ~/.local/bin/ai-jailNeeds Rust 1.97.1.
Install bubblewrap, the small program ai-jail uses to build the sandbox. Most distributions package it as bubblewrap.
pacman -S bubblewrapapt install bubblewrapdnf install bubblewrapNothing to install. ai-jail uses sandbox-exec, which ships with macOS. Apple marks that interface as deprecated. Package registries are not reachable by default on macOS: add one with --allow-host, or open the network with --network.
Not supported. Use WSL2 and follow the Linux steps inside it.
Three commands: a plan, a look inside, a working agent. The login is already inside, and ai-jail opens the route to the agent's API host for you.
↔ Swipe sideways to see the full diagram
Go to your project and ask for a dry run. ai-jail prints the sandbox command it would use and exits. A dry run never writes a file.
cd ~/Projects/my-appai-jail --dry-run claudeStart a plain shell in the jail. Your project is there at its real path and you can edit it. Your home directory is a fresh one, and your shell's tokens are gone. Type exit to leave.
ai-jail bashai-jail mounts the agent's own state folder by default, so it is already signed in. When it knows the agent, it also lets the agent's own API host through its filtered proxy: api.anthropic.com for Claude Code, api.openai.com and chatgpt.com for Codex, generativelanguage.googleapis.com for Gemini, api.x.ai for Grok, server.codeium.com for Devin, on Linux and on macOS. Telemetry hosts stay shut. On Linux, toolchains reach package registries such as crates.io, npm and PyPI by default, so builds work. --network opens the whole network instead.
ai-jail claude# or, with the whole network open:ai-jail --network claudePut a host list in ~/.ai-jail, your own config file, under the agent's name. The list adds to the agent's own API host, which stays allowed, and on Linux github.com and the package registries are already in. Or use --network for the whole network. For a logged-out run, pass --no-agent-state. A .ai-jail file inside the project can only tighten the jail: it can shrink the host list or the forward_ports list and turn the login off, never the opposite, so a repository you clone cannot open the jail for itself.
[commands.claude]allow_hosts = ["api.example.com"]# added to the API host and the registries ai-jail already allows # or, for one run:# ai-jail --network claudeThe prompt changes to (jail) and the project is where it always was. Your toolchains are there too, read only. The things worth stealing are missing, except the agent's own login, which is there on purpose.
$ ai-jail bash# the project is at its real path(jail) ~/Projects/my-app $ pwd/home/you/Projects/my-app# a token exported in your shell did not come along(jail) ~/Projects/my-app $ echo $GITHUB_TOKEN # your keys are not in this home(jail) ~/Projects/my-app $ cat ~/.ssh/id_ed25519cat: /home/you/.ssh/id_ed25519: No such file or directory# the jail has its own hostname(jail) ~/Projects/my-app $ hostnameai-sandboxThis is a Linux session. macOS has no way to build a separate file tree, so there the same paths exist and reading them is refused.
↔ Swipe sideways to see the full diagram
From inside, the project is the only thing of yours that you can change. Your toolchains (mise, cargo, npm and others) are there, read only, with a dependency cache the jail owns. The network stays outside, apart from a known agent's own API host and, on Linux, the package registries those toolchains fetch from, until you open a host with --allow-host, one local port with --forward-port, or all of it with --network. How toolchains are mapped.
Everything the agent starts runs inside with it, so a blocked thing fails, and nothing asks you. This is what that looks like, and how to see it.
--allow-host HOST adds it for the next run.--env NAME or --env-from-file.--audit-log: it records each launch and every connection the proxy allowed or denied. ai-jail --audit-show prints it.ai-jail bash opens a shell inside the same jail.Work on Linux as they are: the registries and github.com are in the default list. On macOS add one, --allow-host registry.npmjs.org, or open everything with --network.
A database, a local MCP server or an editor socket on your machine's loopback: --forward-port 5432 relays that one port on Linux. On macOS, --network.
Clone and pull from GitHub work on Linux. A push needs a credential: --github for your gh token over HTTPS (on macOS add --allow-host github.com), or --network --ssh for your SSH key.
They start inside the jail with the agent. One that reads a token from your shell gets nothing until you pass it, --env GITHUB_TOKEN or --env-from-file. A remote one over HTTP needs its host in --allow-host.
These are the cases the README's troubleshooting covers, plus the two setup rules people trip on.
bwrap: setting up uid map: Permission deniedUbuntu 24.04 and later and Debian 13 and later ship an AppArmor policy that stops programs from creating user namespaces without root, which is how bubblewrap isolates the jail. It affects every rootless tool of this kind. Relax it for the whole system:
echo 'kernel.apparmor_restrict_unprivileged_userns=0' \ | sudo tee /etc/sysctl.d/60-userns.confsudo sysctl --systemOr keep the policy and exempt only bwrap. Save this as /etc/apparmor.d/bwrap, then run sudo apparmor_parser -r /etc/apparmor.d/bwrap.
abi <abi/4.0>,include <tunables/global>profile bwrap /usr/bin/bwrap flags=(unconfined) { userns,}BWRAP_BIN is refusedYou can point ai-jail at a specific bubblewrap with BWRAP_BIN. ai-jail accepts it only when it resolves to a program owned by root that other users cannot write, or to one in a standard multi-user /nix/store. It refuses a copy in your own home, because anyone who can replace that file controls the jail.
Starting background serveropencode 2.x talks to a shared background server. The jail is a one-shot wrapper with a fresh home, so that server does not survive between runs. Start opencode self-contained with --standalone, and pin it in your config so you do not type it each time. ai-jail warns at launch when opencode starts without it:
[commands.opencode]command = ["opencode", "--standalone"]Failed to create stream fd at startupThis comes from a login script on Ubuntu desktop during mise setup. It is harmless and mise still works. Pass --no-mise to skip mise setup and the message with it.
ai-jail --no-mise claudeai-jail knows where each agent keeps its login and mounts it by default: only the folders of the agent you start, and only if they exist. --no-agent-state turns it off. When set in your shell, that agent's API key variable comes along too: ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY or GOOGLE_API_KEY, XAI_API_KEY, MOONSHOT_API_KEY.
| You run | Mounted by default | Good to know |
|---|---|---|
ai-jail claude | ~/.claude~/.claude.json | For a second Claude profile, point --claude-dir at its directory. |
ai-jail codex | ~/.codex | After you resize the window, ai-jail sends Codex a redraw key so the screen repaints. On macOS, non-lockdown Codex gets a private ps shim; with agent state on, it also gets the scoped system services its app server needs. --no-agent-state removes those service grants, not the shim. |
ai-jail gemini | ~/.gemini | |
ai-jail opencode | ~/.config/opencode~/.local/share/opencode | Needs --standalone. See the troubleshooting item above. |
ai-jail antigravity | ~/.gemini | Shares its Google login with Gemini, so one sign in serves both. |
ai-jail kimi | ~/.kimi | ~/.kimi-code, the older location, is mounted too when it exists. |
ai-jail kiro-cli | ~/.kiro~/.local/share/kiro-cli | On macOS the second folder is ~/Library/Application Support/kiro-cli instead. |
ai-jail prime-agent | ~/.prime/agent | Only the agent's own folder, never the whole ~/.prime. |
ai-jail devin | ~/.local/share/devin~/.config/devin | The CLI's own installation under devin/cli/_versions stays read only. Update Devin on the host, then start a new session. |
ai-jail ai-memory run claude | The launched agent's folders | ai-jail recognizes the agent that ai-memory launches and applies that agent's settings, with both programs in one jail. |
The same works for crush, grok, jcode, pi, aider, soulforge and omp. Any other command runs too, with the same jail and no login folder. ai-jail opens the API host by default for the five agents in step 03; other agents need --allow-host or --network.
ai-jail is a single binary that needs no daemon and no root. You add one word in front of the command you already run.
brew tap akitaonrails/tap && brew install ai-jailai-jail claude