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 limitsInstall
Configure

Closed by default. You hold the switches.

With no options, the agent gets only your project, without network, login or keys. You open what a task needs one switch at a time, and you can close the jail further.

Three places a setting can come from

Settings come from a file inside the project, a file in your home directory and the flags you type. They are listed from least to most authority.

Three sources feed the jail. The project .ai-jail file can only tighten it. The global ~/.ai-jail file is trusted. Command-line flags win over both. Inside the jail sits the agent behind a row of switches, most of them off.
A repository you cloned cannot open its own jail.

Project .ai-jail

ai-jail treats it as untrusted: it can tighten the jail and never open it. ai-jail never follows it through a symlink, and the agent sees an empty file in its place.

Global ~/.ai-jail

It is yours, so ai-jail trusts it. It holds a base table for every run, plus [commands.<name>] tables chosen by the first word of the command.

Command-line flags

Flags have the highest authority. Most switches come in pairs, such as --network and --no-network, so a flag can also close what a file opened.

In a command table, single values replace the base and lists such as maps and masks are added to it.

~/.ai-jail
# base: applies to every commandhide_dotdirs = [".my_secrets"]mask = [".env", ".env.*"] # only when the command starts with "claude"[commands.claude]network = trueagent_state = trueenv_pass = ["ANTHROPIC_BASE_URL"]mask = ["*.pem"]

The switches

Every row is off until you ask, and each capability flag has a --no- twin that closes it again. A red badge marks a switch that weakens the jail. Security has the full list and the reasons.

Network and services

FlagConfig keyWhat it doesRisk
--networknetwork = trueUnrestricted network access. Anything the agent can read, it can send out.Weakens the jail
--sshssh = trueYour ~/.ssh, read only, and your SSH agent socket, so git push works.Opens one thing
--dockerno_docker = falseThe Docker socket. This is effectively root on your machine, because the daemon can start containers that mount the host.Weakens the jail
--tailscaletailscale = trueThe Tailscale socket.Opens one thing
--systemd-usersystemd_user = trueThe systemd user bus. The agent can ask your user manager to run services on the host. Linux only.Weakens the jail

Agent and environment

FlagConfig keyWhat it doesRisk
--agent-stateagent_state = trueThe agent's own login and settings, such as ~/.claude or ~/.codex. Anything in the jail can then use those credentials.Opens one thing
--claude-dir <PATH>claude_dir = "..."Uses the given directory as Claude's state and sets CLAUDE_CONFIG_DIR.Opens one thing
--env <NAME[=VALUE]>nonePasses one variable from your shell, or sets one with NAME=VALUE. Repeatable, never saved to disk.Opens one thing
--inherit-envinherit_env = truePasses your whole shell environment, every secret in it included. Avoid it.Weakens the jail
noneenv_pass = ["NAME"]The same as --env, from the global file only. A project file that sets it is ignored.Opens one thing

Desktop and hardware

FlagConfig keyWhat it doesRisk
--gpuno_gpu = falseGPU devices, and with them the driver's attack surface. Linux only.Opens one thing
--displayno_display = falseThe Wayland socket and nothing else from your session. Linux only.Opens one thing
--x11x11 = trueThe X11 socket. X11 lets a program log keystrokes and take screenshots.Weakens the jail
--audioaudio = truePipeWire and PulseAudio sockets and /dev/snd. Anything in the jail can record and play audio. Linux only.Opens one thing
--host-shmhost_shm = trueThe host's /dev/shm, which opens shared memory with processes outside the jail.Opens one thing
--picturespictures = trueYour ~/Pictures, read only.Opens one thing
--browser[=hard|soft]browser_profile = "hard"A separate browser profile. hard (the default) keeps nothing between runs, soft keeps a profile under ~/.local/share/ai-jail/browsers.Opens one thing

Files

FlagConfig keyWhat it doesRisk
--map <PATH|SOURCE:DEST>ro_maps = [...]Mounts one more path read only. Write SOURCE:DEST to mount it somewhere else. Repeatable.Opens one thing
--rw-map <PATH|SOURCE:DEST>rw_maps = [...]Mounts one more path read and write. Repeatable.Opens one thing
--overlay-map <PATH>overlay_maps = [...]Mounts a path copy-on-write: the agent's writes go to a side layer and the original stays untouched. Linux only; on macOS it becomes a read-only mount.Opens one thing
--hide-dotdir <NAME>hide_dotdirs = [...]Never mounts the named dot-directory, for example .my_secrets. Repeatable.

Layers you can turn off, and should not

FlagConfig keyWhat it doesRisk
--no-landlockno_landlock = trueTurns off Landlock, the kernel's own copy of the file rules. Refused under --lockdown.Weakens the jail
--no-seccompno_seccomp = trueTurns off the filter that blocks dangerous system calls.Weakens the jail
--no-rlimitsno_rlimits = trueTurns off the limits that stop runaway processes.Weakens the jail
--no-private-homeprivate_home = falseGives up the fresh home directory. This is broad access to your real home. Prefer --map for the one path you need.Weakens the jail

How it works explains what each layer does.

Utility

FlagWhat it does
--dry-runPrints the sandbox command and runs nothing. It never writes a config file.
--initWrites or updates the project .ai-jail and exits.
--cleanIgnores the project .ai-jail for this run.
--bootstrapGenerates permission configs for the AI tools themselves.
statusShows the current .ai-jail configuration.
-v, --verboseShows every mount as the jail is built.

Run ai-jail --help for the rest: the status bar, worktrees, mise and terminal options.

Hide secrets inside the project

Your .env file lives in the project, and the project is readable and writable. With the network on, the agent can send out whatever it can read there. Masking takes those files away.

A project folder. The agent reaches src, package.json and README.md. The .env file is masked and the secrets folder is denied: both are dim and barred, and no line from the agent reaches them.
The rest of the project stays as it was.

--mask <PATH|GLOB>

Replaces every match with an empty placeholder. The agent sees that the file exists and gets no content, so tools that only check for it keep working.

--deny-path <PATH|GLOB>

Makes every match a permission error.

--mask-except and --deny-path-except

Carve one path back out of a rule. They weaken protection, so a project file cannot set them.

As flags
# quote globs so ai-jail gets the pattern, not your shell's expansionai-jail --mask .env --mask '.env.*' --mask '*.pem' --deny-path secrets/ claude ai-jail --mask '.env.*' --mask-except .env.example claude
.ai-jail
# a project file may tighten, so this works in the repository toomask = [".env", ".env.*", "*.pem"]deny_paths = ["secrets/"]

Lockdown: let an agent read code it must not change

--lockdown is for reviews and audits. The agent can read the project and answer questions about it, and it cannot change a file in it.

Two panels. In normal mode the agent sits inside the wall next to a project folder it can read and write, and a /tmp folder. In lockdown the wall is thicker, the project folder is read only, and /tmp is the only writable place.
The agent and project are the same, and only a temporary directory is writable.

What changes

  • The project is mounted read only
  • Only /tmp is writable on Linux. On macOS nothing is
  • No dot-directories from your home, no extra maps, no overlays
  • The environment is cleared and PATH is fixed to system directories
  • Landlock is mandatory: if it cannot be applied, the launch fails
  • Tighter limits on processes, open files and file size
  • No display, audio, GPU, Docker, SSH agent or mise, whatever else you passed
terminal
ai-jail --lockdown claude

Recipes

Copy one. To see what it builds, run it with --dry-run first.

A cloud agent, day to day

A hosted model needs the network to reach its API and its own login to authenticate. Put both in the global file and the command stays short.

~/.ai-jail
# once, in ~/.ai-jail[commands.claude]network = trueagent_state = true # then, in any projectai-jail claude

Git push over SSH

--ssh mounts ~/.ssh read only and forwards your SSH agent socket. Pushing also needs the network.

terminal
ai-jail --network --ssh --agent-state claude

mise toolchains

The fresh home directory has no mise installs in it, so ai-jail skips mise. Map the two mise directories in, read only.

~/.ai-jail
[commands.claude]ro_maps = ["~/.config/mise", "~/.local/share/mise"]

A browser for the agent

--browser alone gives a browser that cannot load a page or open a window. On Linux it also needs --network and --display, and an X11 browser needs --x11 in place of --display.

terminal
# Waylandai-jail --browser=soft --network --display chromium# X11ai-jail --browser=soft --network --x11 chromium

Separate Claude profiles

Keep work and personal logins apart by pointing each run at its own Claude directory.

terminal
ai-jail --network --claude-dir ~/.claude-work claude

Try a risky refactor on a copy

An overlay keeps the agent's writes in a side layer under .ai-jail-overlays in the project. Compare it with the original afterward and keep what you like. Linux only.

terminal
ai-jail --network --agent-state --overlay-map ~/Projects/my-app/src claude

Old configs keep working

Backward compatibility is a project rule: the project never removes a flag or a config key. Unknown keys are ignored and missing ones take their defaults, so a file written for an old version still loads.

Older switches keep their inverted names, where true turns the thing off: no_gpu, no_docker, no_display, no_mise, no_landlock, no_seccomp, no_rlimits. That is why --gpu is saved as no_gpu = false. Newer switches use plain names such as network = true.

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