github akitaonrails/ai-jail v2.5.0

5 hours ago

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/rustc resolve, and binds ~/.cargo/{registry,git} to the jail store;
    ~/.cargo credentials and config.toml are 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 install to
    the global bin is unsupported in-jail (use cargo 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-network keeps the sandbox fully offline;
    --network gives unrestricted access; browser and --lockdown launches never
    trigger it; an explicit --allow-host keeps 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/kvm through 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 handle LANDLOCK_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 into deps/), rustup, and
    atomic-save editors. Every stacked layer now handles and grants Refer on 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-file is read through a validated file descriptor. Credential
    files are now opened and validated on the same descriptor they are read from
    (openat with O_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-network for a fully offline launch, or
    --no-toolchains to turn off both the cache maps and the registry egress.
  • An agent that talks to its own API (for example claude reaching
    api.anthropic.com) still needs --network or 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

Don't miss a new ai-jail release

NewReleases is sending notifications on new releases.