Skip to content
Menu
Do I need it?What an agent can reach on your machineHow it worksThe layers between the agent and your systemCompareBuilt-in sandboxes, Docker and VMsConfigureOpen only what a task needsSecurityThe threat model and its limitsDownloadBinaries, checksums and release notesInstall
Security

A useful layer, with known edges

ai-jail is built to stop a trusted agent's mistakes and keep the damage inside the project: the project directory is the blast radius, and your home, your keys and the network stay outside it. It is a process sandbox that shares your kernel, so it is not a boundary against hostile code or a motivated attacker and does not replace a disposable virtual machine.

For the person who signs off, then for the security engineer.

What it is built to stop

A trusted agent still mistypes a command, runs a script that walks above the project root, or gets misled by a web page or a package. These are the things it reaches for next and does not find.

↔ Swipe sideways to see the full diagram

The defaults. Every item on the outside stays there until you open it yourself.

Out of reach

  • Your home directory and the credentials in it. The agent gets a fresh, empty home, so ~/.ssh and ~/.aws do not exist in there.
  • Secrets in your shell. ai-jail cuts environment variables down to a small allowlist, so exported tokens stay outside. The one exception is the invoked harness's own API key, for example ANTHROPIC_API_KEY for Claude, copied by default along with its login; --no-agent-state stops that.
  • The network. It is closed, with two exceptions that pass through the filtered proxy: a short list of package registries when toolchains are on, the default on Linux, and, for a known agent, its own API host, on both platforms. Telemetry hosts stay closed. --no-network closes both.
  • Host shared memory, your display server and your clipboard. Agent output passes through a filter that drops terminal clipboard and query sequences.
  • Typing into your terminal. Linux blocks the TIOCSTI call that fakes keystrokes, and macOS limits terminal control to the one terminal ai-jail created for the run.
  • Its own rules. A .ai-jail file inside a repository is untrusted: it can tighten the sandbox and never open it.
How each layer does this

Where the wall ends

The jail and your system stand on the same kernel. A bug in that kernel, in a driver or in the sandbox backend is a way around the wall, and ai-jail cannot do anything about it. A virtual machine brings its own kernel, which is why it is the stronger boundary.

↔ Swipe sideways to see the full diagram

A sandbox shares the ground with what it protects, and a VM brings its own.

What this means for your data

  • Secrets inside the project. A .env file in the repository is readable unless you mask it. See how to mask files.
  • Masks cover only paths that exist when the sandbox starts. A secret file created later, during the session, is not hidden.
  • An allowed host is trusted wholesale. Allowing github.com with --allow-host lets the agent push to any repository there, not only yours.
  • --network opens the whole network, so anything the agent can read, your project included, it can send out.
  • ai-jail mounts the agent's own login by default, read-write, so a misled agent can use it for the life of the launch. The project documents this as a convenience trade; --no-agent-state gives a logged-out run.
  • Host-side git trusts the project's .git/, which is writable. A program named in .git/config, such as core.fsmonitor or core.pager, or dropped into .git/hooks/ runs as you, outside the sandbox, the next time a matching git command runs on the host; for core.fsmonitor a plain git status is enough, even one your editor runs in the background, and nothing shows in git diff. --lockdown closes it by making the project read only, and --map .git keeps the repository read only in a normal run, at the cost of committing from inside. After a session that surprised you, read .git/config and list .git/hooks with plain file tools before you run git on the host, since git config --list itself starts the configured pager.
  • --mask and --deny-path change only what the sandbox sees. Anything outside the sandbox that acts for the agent still reads the real files: the Docker daemon under --docker, an MCP server reached through a mapped socket, or a service behind --forward-port.
For the security engineer
  • Kernel and driver vulnerabilities. Every sandboxed process talks to the same kernel as the rest of your machine.
  • Bugs in your terminal emulator, above all with --terminal-passthrough, which turns the output filter off.
  • Defects in the sandbox backend itself: bubblewrap, Landlock and seccomp on Linux, sandbox-exec on macOS.
  • Landlock, the kernel's own rules, fences abstract Unix sockets and signals only on kernels 6.12 and newer, best effort. Older kernels keep only the filesystem and network rules, so with --x11, --network or --systemd-user the host's abstract sockets and signals stay reachable.
  • Side channels and some classes of inter-process communication.
  • Filtered egress is TCP only. There is no UDP, so a program that needs it does not work; it also cannot leak through it.
  • DNS under --allow-host. On Linux, name lookups inside the sandbox fail, so there is no DNS channel out. On macOS the system resolver is not fenced, so a low-bandwidth DNS channel remains, the same gap Anthropic documents for its own sandbox-runtime.
  • The proxy sees the host, the port and the timing of each connection. What travels inside stays between the two ends. How filtered egress is built.
  • Phantom credentials move trust to ai-jail itself. With --secret, requests to the bound host pass through ai-jail in clear so it can insert the real key, and it re-encrypts them on the way out. Tunnels to every other host stay opaque. It only covers clients that can talk plain HTTP to the proxy for that host.
  • Credentials you pass with --github, --aws, --kube, --gcloud or --docker-config. Read-only protects the file from changes, not the credential from use: anything in the sandbox can act as you on that service while it is valid.
  • On macOS, sandbox-exec is deprecated by Apple and is not equivalent to the Linux isolation. See the macOS differences.

Switches that open the wall

Everything below is off until you turn it on, except two switches that are on by default: --toolchains on Linux and --agent-state everywhere. Most of them give the agent real access. How to set them is on the Configure page.

Show all switches and consequences
SwitchWhat anything in the sandbox can then do
--networkSend everything it can read anywhere, with no restriction on traffic. The default allows only a known agent's own API host and, on Linux, the package registries; --allow-host is the tighter alternative.
--allow-hostReach exactly the hosts you list, and their subdomains, over TCP through a built-in proxy. Each listed host is trusted wholesale. Your list adds to the agent's own API host; --no-network with a list gives you that list alone.
--forward-portUse one service on your host's loopback in full. Whatever the MCP server, editor socket or database on that port accepts, the agent can ask for, with no allowlist, no inspection and no audit record on it. Every other host port stays shut. Linux only, and refused with --network.
--toolchainsOn by default on Linux. Run your toolchains: ~/.cargo/bin and ~/.rustup read-only, a jail-owned dependency cache under ~/.local/share/ai-jail/cache/ that survives between runs, and filtered egress to the package registries. ai-jail never binds the host's own caches. Off under --lockdown and with --no-toolchains.
--secretNothing new. The real value of that key never enters the sandbox: the agent sees a placeholder, and the proxy swaps in the real value only for plain-HTTP requests to the one host you bound it to. ai-jail's own process sees that request in clear.
--audit-logNothing new. This switch weakens nothing: it writes one line per launch to a file with mode 0600 outside the sandbox, so the agent never sees it.
--dockerAct as root on your machine through the Docker daemon.
--kvmOpen /dev/kvm, the kernel's virtualization interface, so QEMU, Firecracker or the Android emulator run at hardware speed. Linux only. Guests get only the sandbox's network and no /dev/net/tun.
--x11Log keystrokes and take screenshots of other X11 windows.
--systemd-userAsk your systemd user manager to run services on the host.
--inherit-envRead your whole shell environment, secrets included. For API keys, --env-from-file passes one file instead.
--agent-stateOn by default. Use the agent's own saved login, for example Claude's ~/.claude or Devin's ~/.local/share/devin and ~/.config/devin, mounted read-write, plus its API key copied from your shell when set, so it starts logged in. Anything in the sandbox can use those credentials for the life of the launch. --no-agent-state gives an isolated, logged-out run; off under --lockdown.
--githubAct as you on GitHub. The gh config directory is mounted read-only, and the active github.com token is read from your keyring on the host and forwarded as GH_TOKEN for that launch, never on the command line or on disk. Read-only protects the file; the token still works for as long as it is valid.
--awsAct as you on AWS with ~/.aws, mounted read-only.
--kubeAct as you on every cluster in ~/.kube, mounted read-only.
--gcloudAct as you on Google Cloud with ~/.config/gcloud, mounted read-only.
--docker-configUse your container registry logins in ~/.docker/config.json, mounted read-only.
--audioRecord and play audio while it is on.
--no-private-homeSee your real home directory. This is a broad exception; prefer a map of the one path you need.

When in doubt, it refuses to start

A sandbox that silently starts weaker than you asked is worse than one that stops with an error. In these cases ai-jail stops.

  • A config file that exists and is invalid or unreadable.
  • A project .ai-jail that is a symlink. A symlinked global ~/.ai-jail is followed only to a regular file you own, that no one else can write, outside the project.
  • --lockdown when Landlock, the kernel's own filesystem rules, is missing, partial or fails to apply. --lockdown --no-landlock is rejected too.
  • --allow-tcp-port. That interface cannot constrain UDP, so the flag is accepted for compatibility, the launch fails, and the error points at --allow-host.
  • --network together with --allow-host, or --browser with --allow-host. The two contradict each other, so ai-jail refuses to launch.
  • --forward-port together with --network, where the host loopback is already reachable; outside Linux; and for port 0 or a port below 1024, which the sandbox cannot bind. A forward_ports entry in the base table of the global file hits the first two on every browser launch and every macOS launch.
  • An --env-from-file file that is a symlink, readable by its group or by everyone, owned by someone else or inside the project.
  • A BWRAP_BIN that points at a bubblewrap binary someone other than root could have replaced. It is ignored with a warning, and with no trusted bubblewrap the launch fails.
  • An overlay that cannot be set up: a missing source, a destination that overlaps another map, or a storage failure. Since 2.4 the launch aborts instead of falling back to a plain read-write bind of the real files.

How the project is checked

One maintainer runs the project in the open. Below is what gets checked and what does not.

23
sandbox-escape integration tests that try to write to /usr, read ~/.ssh, call ptrace and bpf, and expect to fail
959
unit tests in the source tree at version 2.9.0
0
network requests at launch. The update check is opt-in with --update-check

Recent security fixes

  • 2.9.0: --x11 alone passes X11 through. Before, it silently did nothing without --display.
  • 2.9.0: Auto-save no longer writes opt-ins into the project file that the untrusted load then refuses, so a saved file never warns on the next run.
  • 2.9.0: macOS: Codex launches again under Seatbelt with a private ps shim in place of the setuid host ps.
  • 2.8.0: macOS: the seatbelt profile keeps Claude Code's Keychain login and allows the Codex control socket, so both launch logged in.
  • 2.8.0: Cursor-position requests from a launcher are answered by the virtual terminal, so launchers no longer stall.
  • 2.6.2: --lockdown always isolates the network namespace, even with --network. Before, that contradictory pair left the namespace shared.
  • 2.6.2: --env and --env-from-file values no longer sit on the launcher's command line, where any process running as you could read them. They pass through an in-memory file.
  • 2.4.0: A repository's worktree metadata can no longer bind an arbitrary host directory read-write.
  • 2.4.0: A duplicate environment entry no longer slips the real value of a --secret key into the sandbox.
  • 2.4.0: A browser command in an untrusted project .ai-jail no longer clears lockdown.
  • 2.4.0: TIOCSTI is compared as the kernel reads it, 32-bit, so a high-bit variant no longer passes seccomp.
  • 2.4.0: Landlock scopes abstract Unix sockets and signals on kernels 6.12 and newer.
  • 2.4.0: macOS: terminal access is scoped to this run's own terminals, not every /dev/ttys*.
  • 2.4.0: macOS: --mask paths are denied for writes as well as reads.
  • 2.4.0: The output filter no longer collapses on a doubled ESC, which could smuggle a clipboard write past it.
  • 2.4.0: The proxy's test-only escape hatches are compiled out of release builds.

How a release is made

  • Release tags are signed, and CI verifies each one against a key fingerprint pinned in the repository.
  • Every GitHub Action is pinned to a commit SHA, and actions are restricted to an allowlist.
  • Builds use a pinned Rust toolchain and --locked dependencies.
  • macOS binaries are signed and notarized by Apple.
  • Each archive ships with a SHA-256 checksum, and the publish job checks them again.

What the release document says is still missing

  • Signing and publishing secrets are still stored at repository level and should move into the release environments.
  • No ruleset yet forces every v* tag to be signed.
  • Immutable releases are not enabled.
  • crates.io publishing still uses a token. Trusted publishing is planned.
  • With a single maintainer there is no second reviewer on changes or releases.

Read RELEASE_SECURITY.md

Report a vulnerability

Do not open a public issue. Use GitHub's private vulnerability reporting, include the steps to reproduce and the affected versions, and allow time to coordinate a fix.

Common questions

Can the agent break out?
The defaults are built and tested to block the ordinary routes a mistaken agent takes: files, environment, network and host IPC. They are not built to resist a determined attacker: something that actively tries to escape could use a kernel, driver, terminal or sandbox-backend bug, which is why hostile code belongs in a disposable VM.
Is it safe to run with the network on?
Safer than running the agent bare, because your keys and home directory are still not there to send. With --network, anything the agent can read, it can send, and ai-jail is not built to stop a determined attacker from doing so. The middle ground is --allow-host: the agent reaches only the hosts you list, and each one is trusted as a whole. Mask the secrets inside the project first. See how to mask them.
Has it been audited?
In the open, by the project itself before 1.0 and by two independent source audits of 2.3.0 published in the repository in September 2026; 2.4.0 closed the two advisories they produced. No third-party firm has audited it. The code is open under GPL 3.0, and private vulnerability reports are welcome.

Put your agent behind bars

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.

terminal
brew tap akitaonrails/tap && brew install ai-jailai-jail claude