A feature release that makes real development inside the jail work: dependency
caches now persist across sessions, the Rust toolchain works out of the box,
package registries are reachable by default (filtered), common tool credentials
can be shared read-only on request, and /dev/kvm can be passed through for
hardware virtualization. It also folds in two security fixes that were blocking
toolchain use.
Backward compatible: every pre-2.5.0 .ai-jail keeps working. The new config
keys default to the prior behavior (except toolchains, which is on by default
and additive), and all new capabilities are monotonic — an untrusted project
.ai-jail can only disable them, never enable them.
Added
-
Dev-toolchain cache persistence (
--toolchains/--no-toolchains, on by
default; Linux). ai-jail maps a persistent, jail-owned cache store at
~/.local/share/ai-jail/cache/so dependency caches survive across sessions
instead of being thrown away with the private home each launch. For Rust it
also maps the toolchain binaries read-only (~/.cargo/bin,~/.rustup) so
cargo/rustcresolve, and binds~/.cargo/{registry,git}to the jail store;
~/.cargocredentials andconfig.tomlare never mapped. Other ecosystems get
a jail-owned cache at their default path when present on the host:~/.cache
(pip/uv, go-build, yarn, deno, coursier, crystal, zig, composer),~/go/pkg/mod,
~/.npm,~/.m2/repository,~/.gradle/caches,~/.bun/install/cache,
~/.local/share/pnpm/store. The host's real caches are never bound, so a jailed
build cannot poison what the host's own builds compile from.cargo installto
the global bin is unsupported in-jail (usecargo install --root). Disabled
under--lockdown. -
Default filtered egress to package registries. When toolchains are on and
the network posture is otherwise unset, ai-jail default-allows filtered egress
to the registries dependency managers need (crates.io,registry.npmjs.org,
registry.yarnpkg.com,pypi.org,files.pythonhosted.org,
proxy.golang.org,sum.golang.org,repo1.maven.org,repo.maven.apache.org,
repo.clojars.org,repo.packagist.org,github.com,codeload.github.com,
objects.githubusercontent.com). Deny-by-default still holds — only those
hosts are reachable. It is gated on an unprivileged-network-namespace probe:
where netns is unavailable, the default silently stays offline rather than
breaking the launch.--no-networkkeeps the sandbox fully offline;
--networkgives unrestricted access; browser and--lockdownlaunches never
trigger it; an explicit--allow-hostkeeps its fail-closed guarantee and is
unioned with the registries. -
Opt-in read-only credential passthrough.
--github(~/.config/gh),
--aws(~/.aws),--kube(~/.kube),--gcloud(~/.config/gcloud), and
--docker-config(~/.docker/config.json) each mount that tool's credentials
read-only so the tool works in the jail. All off by default and disabled under
--lockdown. This is a deliberate trust extension: read-only protects the file
from modification, not the credential from use — anything in the sandbox can
then act as you on that service — so each stays opt-in. -
--kvm/--no-kvm(Linux). Passes/dev/kvmthrough for
hardware-accelerated virtualization (QEMU, Firecracker, the Android emulator).
Bound only when it is a character device, off by default, disabled under
--lockdown. Thanks to @thomaswiese (#143).
Fixed
-
Landlock no longer blocks cross-directory
rename(2)(EXDEV). The V6
scope ruleset added in v2.4.0 was stacked as a separate Landlock layer that did
not handleLANDLOCK_ACCESS_FS_REFER; such a layer forbids every
cross-directory rename/link for the restricted process, even when the
filesystem layer allows it. This silently broke all Rust compilation in the
jail (rustc stages each output and renames it intodeps/),rustup, and
atomic-save editors. Every stacked layer now handles and grantsReferon the
writable paths (never on read-only ones, so reparenting into or out of a
read-only/denied tree stays blocked). -
--env/--network/etc. after a--separator are forwarded to the child.
The post-command flag guard scanned the whole argument vector and rejected any
child flag whose name collided with an ai-jail flag, even when--was already
present. The argument vector is now split on the first--: everything after it
is the command and its arguments, verbatim (and--is not passed to the
child). The ambiguous no---case still errors. -
--env-from-fileis read through a validated file descriptor. Credential
files are now opened and validated on the same descriptor they are read from
(openat withO_NOFOLLOW, outside-project ancestry checked on the opened
directory, non-regular/FIFO refused), closing a time-of-check/time-of-use gap.
File and directory permissions are never changed. Thanks to @Josehbr (#146).
Notes
- The default registry egress softens the strict "network off by default" posture
for the toolchain case only, and always as filtered (deny-by-default) egress,
never unrestricted. Use--no-networkfor a fully offline launch, or
--no-toolchainsto turn off both the cache maps and the registry egress. - An agent that talks to its own API (for example
claudereaching
api.anthropic.com) still needs--networkor an explicit--allow-host;
the default egress covers package registries, not arbitrary service APIs.
Checksums (SHA256)
0735197a80f211d147d04c08c07ff139b9906d36b8d0137c974c2e8fa90a2d15 ai-jail-linux-x86_64.tar.gz
9dfa16a3a887f780f8b12994017c3d85f2384ad082082b8db9cc63ba7c467ee1 ai-jail-macos-aarch64.tar.gz
Install
brew tap akitaonrails/tap && brew install ai-jail
yay -S ai-jail-bin
cargo install --locked ai-jail