ai-jail
A sandbox wrapper for AI coding agents (Linux: bwrap, macOS: sandbox-exec). Isolates tools like Claude Code, GPT Codex, OpenCode, and Crush so they can only access what you explicitly allow.
Install
Homebrew (macOS / Linux)
&&
cargo install
mise
Nix (flake)
# Run directly without installing
# Install to your profile
GitHub Releases
Download prebuilt binaries from the Releases page:
# Linux x86_64
|
# macOS ARM (Apple Silicon)
|
From source
Dependencies
- Linux: bubblewrap (
bwrap) must be installed:- Arch:
pacman -S bubblewrap - Debian/Ubuntu:
apt install bubblewrap - Fedora:
dnf install bubblewrap - If
bwrapis in a non-standard location (e.g. Nix store), setBWRAP_BIN=/absolute/path/to/bwrap. - The Nix flake package already sets
BWRAP_BINautomatically.
- Arch:
- macOS:
/usr/bin/sandbox-execis used (legacy/deprecated Apple interface).
Quick Start
# Run Claude Code in a sandbox
# Run bash inside the sandbox (for debugging)
# See what the sandbox would do without running it
On first run, ai-jail creates a .ai-jail config file in the current directory. Subsequent runs reuse that config. Commit .ai-jail to your repo so the sandbox settings follow the project.
Security notes
The default mode favors usability over maximum lockdown. These are intentionally open by default:
- Docker socket passthrough auto-enables when
/var/run/docker.sockexists (--no-dockerdisables it). - Display passthrough mounts
XDG_RUNTIME_DIRon Linux, which can expose host IPC sockets. - Environment variables are inherited (tokens/secrets in your shell env are visible in-jail).
Defense-in-depth layers (Linux)
ai-jail applies multiple overlapping security layers:
- Namespace isolation (bwrap): PID, UTS, IPC, mount namespaces. Network namespace in lockdown.
- Landlock LSM (V3 filesystem + V4 network): VFS-level access control independent of mount namespaces.
- Seccomp-bpf syscall filter: blocks ~30 dangerous syscalls (module loading,
ptrace,bpf, namespace escape, etc.). Lockdown blocks additional NUMA/hostname syscalls. - Resource limits: RLIMIT_NPROC (4096/1024 lockdown), RLIMIT_NOFILE (65536/4096 lockdown), RLIMIT_CORE=0. Prevents fork bombs and limits resource abuse.
- Sensitive /sys masking: tmpfs overlays hide
/sys/firmware,/sys/kernel/security,/sys/kernel/debug,/sys/fs/fuse. Lockdown also masks/sys/module,/sys/devices/virtual/dmi,/sys/class/net.
Each layer can be individually disabled (--no-seccomp, --no-rlimits, --no-landlock) if it causes issues.
For hostile/untrusted workloads, use --lockdown (see below).
What this is and isn't
ai-jail is a thin wrapper around OS-level sandboxing, so its security properties depend on the backend:
bwrap(Linux): namespace + mount sandboxing in userspace, plus Landlock LSM for VFS-level access control (Linux 5.13+).sandbox-exec/ seatbelt (macOS): legacy policy interface to Apple sandbox rules.
Some things to keep in mind:
- All backends depend on host kernel correctness. Kernel escapes are out of scope.
- These are process sandboxes, not hardware isolation. A VM runs a separate kernel and gives a stronger boundary.
- Timing/cache side channels and scheduler interference still exist in process sandboxes.
- Linux and macOS primitives are not equivalent; cross-platform policy parity is approximate.
sandbox-execon macOS is a deprecated interface. It works today but Apple could remove it.
If you need full isolation against unknown malware, use a disposable VM and treat ai-jail as one layer, not the whole story.
Lockdown mode
--lockdown switches to strict read-only, ephemeral behavior for hostile workloads.
This:
- Mounts the project read-only.
- Disables GPU, Docker, display passthrough, and mise.
- Ignores
--rw-mapand--mapflags. - Mounts
$HOMEas bare tmpfs (no host dotfiles). - Linux:
--clearenvwith minimal allowlist,--unshare-net,--new-session. - macOS: clears env to minimal allowlist, strips network and file-write rules from SBPL profile.
Persistence: --lockdown alone doesn't write .ai-jail (keeps runs ephemeral). Persist it with ai-jail --init --lockdown. Undo with --no-lockdown.
What gets sandboxed
Default behavior (no flags needed)
| Resource | Access | Notes |
|---|---|---|
/usr, /etc, /opt, /sys |
read-only | System binaries and config |
/dev, /proc |
device/proc | Standard device and process access |
/tmp, /run |
tmpfs | Fresh temp dirs per session |
$HOME |
tmpfs | Empty home, then dotfiles layered on top |
| Project directory (pwd) | read-write | The whole point |
GPU devices (/dev/nvidia*, /dev/dri) |
device | For GPU-accelerated tools |
| Docker socket | read-write | If /var/run/docker.sock exists |
| X11/Wayland | passthrough | Display server access |
/dev/shm |
device | Shared memory (Chromium needs this) |
In --lockdown, project is mounted read-only and host write mounts are removed.
Home directory handling
Your real $HOME is replaced with a tmpfs. Dotfiles and dotdirs are selectively layered on top:
Never mounted (sensitive data):
.gnupg,.aws,.ssh,.mozilla,.basilisk-dev,.sparrow
Mounted read-write (AI tools and build caches):
.claude,.crush,.codex,.aider,.config,.cargo,.cache,.docker
Everything else: mounted read-only.
Hidden behind tmpfs:
~/.config/BraveSoftware,~/.config/Bitwarden~/.cache/BraveSoftware,~/.cache/chromium,~/.cache/spotify,~/.cache/nvidia,~/.cache/mesa_shader_cache,~/.cache/basilisk-dev
Explicit file mounts:
~/.gitconfig(read-only)~/.claude.json(read-write)
Local overrides (read-write):
~/.local/state~/.local/share/{zoxide,crush,opencode,atuin,mise,yarn,flutter,kotlin,NuGet,pipx,ruby-advisory-db,uv}
Namespace isolation
PID, UTS, and IPC namespaces are isolated. Hostname inside is ai-sandbox. The process dies when the parent exits (--die-with-parent).
--new-session is on for non-interactive runs and always in --lockdown. In --lockdown, Linux also unshares network.
Landlock LSM (Linux)
On Linux 5.13+, ai-jail applies Landlock restrictions as defense-in-depth on top of bwrap. Landlock controls what the process can do at the VFS level, independent of mount namespaces. This closes attack vectors that bwrap alone doesn't cover: /proc escape routes, symlink tricks within allowed mounts, and acts as insurance against namespace bugs.
- Uses ABI V3 (Linux 6.2+) for filesystem rules with best-effort degradation to V1 on 5.13+ or no-op on older kernels.
- On Linux 6.5+, a second V4 ruleset adds network restrictions: lockdown mode denies all TCP bind/connect (defense-in-depth alongside
--unshare-net). - Applied in the parent process before spawning bwrap, so restrictions inherit through fork+exec.
- In
--lockdown, Landlock rules are stricter: project is read-only, no home dotdirs, only/tmpis writable, no network. - Disable with
--no-landlockif it causes issues with specific tools.
Status bar
Enable a persistent status line on the bottom row of your terminal:
The bar shows the project path, running command, ai-jail version, and a green ↑ when an update is available. It uses a PTY proxy to keep the bar visible even when the child application resets the screen. The preference is stored in $HOME/.ai-jail and persists across sessions.
mise integration
If mise is found on $PATH, the sandbox automatically runs mise trust && mise activate bash && mise env before your command. This gives AI tools access to project-specific language versions. Disable with --no-mise.
Usage
ai-jail [OPTIONS] [--] [COMMAND [ARGS...]]
Commands
| Command | What it does |
|---|---|
claude |
Run Claude Code |
codex |
Run GPT Codex |
opencode |
Run OpenCode |
crush |
Run Crush |
bash |
Drop into a bash shell |
status |
Show current .ai-jail config |
| Any other | Passed through as the command |
If no command is given and no .ai-jail config exists, defaults to bash.
Options
| Flag | Description |
|---|---|
--rw-map <PATH> |
Mount PATH read-write (repeatable) |
--map <PATH> |
Mount PATH read-only (repeatable) |
--lockdown / --no-lockdown |
Enable/disable strict read-only lockdown mode |
--landlock / --no-landlock |
Enable/disable Landlock LSM (Linux 5.13+, default: on) |
--seccomp / --no-seccomp |
Enable/disable seccomp syscall filter (Linux, default: on) |
--rlimits / --no-rlimits |
Enable/disable resource limits (default: on) |
--gpu / --no-gpu |
Enable/disable GPU passthrough |
--docker / --no-docker |
Enable/disable Docker socket |
--display / --no-display |
Enable/disable X11/Wayland |
--mise / --no-mise |
Enable/disable mise integration |
-s, --status-bar[=light] |
Enable persistent status line (dark or light theme) |
--no-status-bar |
Disable persistent status line |
--exec |
Direct execution mode (no PTY proxy, no status bar) |
--clean |
Ignore existing config, start fresh |
--dry-run |
Print the bwrap command without executing |
--init |
Create/update config and exit (don't run) |
--bootstrap |
Generate smart permission configs for AI tools |
-v, --verbose |
Show detailed mount decisions |
-h, --help |
Show help |
-V, --version |
Show version |
Examples
# Share an extra library directory read-write
# Read-only access to reference data
# No GPU, no Docker, just the basics
# Run a one-shot command and capture its output
result=
# Suspicious/untrusted workload mode
# See exactly what mounts are being set up
# Create config without running
# Regenerate config from scratch
# Pass flags through to the sub-command (after --)
Config file (.ai-jail)
Created in the project directory on first run. Example:
# ai-jail sandbox configuration
# Edit freely. Regenerate with: ai-jail --clean --init
= ["claude"]
= ["/home/user/Projects/shared-lib"]
= []
= true
= true
Merge behavior
When CLI flags and an existing config are both present:
command: CLI replaces configrw_maps/ro_maps: CLI values are appended (duplicates removed)- Boolean flags: CLI overrides config (
--no-gpusetsno_gpu = true) - Config is updated after merge in normal mode; lockdown skips auto-save
Available fields
| Field | Type | Default | Description |
|---|---|---|---|
command |
string array | ["bash"] |
Command to run inside sandbox |
rw_maps |
path array | [] |
Extra read-write mounts |
ro_maps |
path array | [] |
Extra read-only mounts |
no_gpu |
bool | not set (auto) | true disables GPU passthrough |
no_docker |
bool | not set (auto) | true disables Docker socket |
no_display |
bool | not set (auto) | true disables X11/Wayland |
no_mise |
bool | not set (auto) | true disables mise integration |
no_landlock |
bool | not set (auto) | true disables Landlock LSM (Linux only) |
no_seccomp |
bool | not set (auto) | true disables seccomp syscall filter (Linux only) |
no_rlimits |
bool | not set (auto) | true disables resource limits |
lockdown |
bool | not set (disabled) | true enables strict read-only lockdown mode |
Status bar preferences (no_status_bar, status_bar_style) are stored in $HOME/.ai-jail (global user config), not in per-project .ai-jail files.
When a boolean field is not set, the feature is enabled if the resource exists on the host.
Windows
ai-jail doesn't support Windows natively and probably never will. The sandbox depends on Linux namespaces (via bwrap) and macOS seatbelt profiles (via sandbox-exec). Windows has nothing equivalent in userspace. AppContainers exist but they're a completely different API, need admin privileges for setup, and the security model doesn't map to what bwrap does. A Windows port would be a separate project, not a backend swap.
If you're on Windows, run ai-jail inside WSL 2. WSL 2 runs a real Linux kernel, so bwrap works normally.
Setup
- Install WSL 2 if you haven't:
wsl --install
- Open your WSL distro (Ubuntu by default) and install bubblewrap:
&&
- Build ai-jail from source inside WSL:
- Run it from inside WSL against your project directory:
WSL 2 mounts your Windows drives under /mnt/c/, /mnt/d/, etc. The sandbox sees the Linux filesystem, so all the mount isolation works as expected. Your Windows files are accessible through those mount points.
One thing to watch: WSL 2 filesystem performance is slower on /mnt/c/ (the Windows side) than on the native Linux filesystem (~/). For large projects, cloning into ~/Projects/ inside WSL instead of working from /mnt/c/ makes a noticeable difference.
License
GPL-3.0. See LICENSE.