sys-apps/ai-jail
Sandbox for AI coding agents (bubblewrap on Linux, sandbox-exec on macOS)
ChangeLog
commit 988d12bc3ec879fe787b560f38809dabdb8fa081
Author: lucascouts <lucascs@protonmail.com>
Date: Mon Sep 7 12:28:34 2026 -0300
fix(sys-apps/{ai-jail,ai-jail-bin}): USE=chromium-launcher installed a menu entry that could not open a window, add(2 patches), rev(1.20.2 -> 1.20.2-r1)
Upstream's dist/desktop/ai-jail-chromium runs
exec ai-jail --browser=soft chromium "$@"
and nothing else. --browser only arranges the isolated browser profile: on
Linux the sandbox still unshares the network namespace and mounts no display
socket, so the launcher produced a Chromium that could neither open a window
nor load a page. Upstream's own README states this -- "--browser alone
produces a browser that cannot load pages -- and on Linux cannot open a window"
-- but the launcher it ships does not act on it.
Measured, not inferred. `ai-jail --dry-run` prints the backend invocation:
--browser=soft -> --unshare-net, no wayland/X11 bind
--browser=soft --network --display -> shared net, /run/user/1000/wayland-0
The previous revision was verified only to the point of installing the two
files in the right places; that it did nothing useful once clicked went
unmeasured. That is the defect this revision fixes, and the reason the check
below now executes the installed script instead of inspecting it.
The patch grants both capabilities and picks the display flag from the session
actually in use -- --display for Wayland, --x11 for X11 -- and fails with a
clear message when neither is present, rather than launching into nothing.
--network is unrestricted network for everything inside the sandbox. For a
browser that is the point; the isolation that remains, and that motivates the
launcher, is the filesystem one: a private tmpfs $HOME, with only
~/.local/share/ai-jail/browser-launcher-cwd writable, so the browser profile
persists and nothing else in $HOME is visible.
Both patches are named without a version. The autoupdate applier renames
ebuilds but never files/, so a versioned patch name would be orphaned on the
next bump and PATCHES would die.
ai-jail-bin now copies the two launcher distfiles into $ in src_unpack
instead of installing them straight from $, so the script goes through
src_prepare like any other source file. The copy, and the patch, are gated on
the flag, since the files are only fetched when it is on.
Verified locally, without root, for both packages and both USE settings:
- four builds, all exit 0; patch applies in each launcher-enabled build
- USE=-chromium-launcher installs no launcher file at all (measured, 0 hits)
- the INSTALLED script, run against a stub ai-jail that echoes its argv:
WAYLAND_DISPLAY set -> ai-jail --browser=soft --network --display chromium
DISPLAY set -> ai-jail --browser=soft --network --x11 chromium
neither set -> diagnostic on stderr, exit 1
and a %U-style URL argument is forwarded intact
- shellcheck -s sh on the patched script: no findings
- pkgcheck on both: only the known musl NonsolvableDepsInDev, unchanged
Nothing was merged for real -- this host has no sudo.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit 6318264191abfac21d6859146c4cc45bf83fd80b
Author: lucascouts <lucascs@protonmail.com>
Date: Mon Sep 7 11:55:52 2026 -0300
add(sys-apps/{ai-jail-1.20.2,ai-jail-bin-1.20.2}), mod(.autoupdate/packages.toml, profiles/use.local.desc): package akitaonrails/ai-jail, source and prebuilt, blocked one-sided
ai-jail runs AI coding agents inside an OS sandbox -- bwrap plus Landlock,
seccomp and rlimits on Linux. GPL-3.0-only, single Rust binary, no daemon and
no systemd unit, so the overlay's OpenRC-coverage rule does not apply.
sys-apps, not app-containers: the Gentoo precedent for process sandboxes is
sys-apps/bubblewrap, sys-apps/firejail and sys-apps/minijail. app-containers
holds runtimes, not wrappers.
Two packages, because they cover different arches. The from-source ebuild is
~amd64 ~arm64; upstream publishes exactly two release assets, linux-x86_64 and
macos-aarch64, so the -bin has no Linux arm64 build to ship and stays
"-* ~amd64". arm64 coverage comes entirely from source. On a bump, read the
ASSET LIST before touching KEYWORDS -- the tag says nothing about it.
The block is weak and declared only in ai-jail-bin, following this overlay's
own precedent in sys-apps/uutils-coreutils-bin. Both install /usr/bin/ai-jail
and cannot coexist, but nothing in the tree RDEPENDs on ai-jail, so portage can
order the swap itself instead of stopping to make the user emerge -C by hand.
USE=chromium-launcher (default off) installs upstream's dist/desktop pair.
Those two files live in the git tree, not in the release tarball, so the -bin
fetches them from raw.githubusercontent.com pinned to the same tag; they are
plain files, hence the custom src_unpack and S="$".
src_prepare deletes "strip = true" from [profile.release]: upstream hands
portage a pre-stripped binary otherwise, which trips the QA check and makes
splitdebug produce empty objects. The sed is guarded by grep -qF so it dies
loudly if upstream drops the line, rather than silently becoming a no-op.
src_test runs the unit tests only. tests/sandbox_escape.rs needs working
unprivileged user namespaces, which the portage sandbox does not provide;
upstream's own PKGBUILD skips them for the same reason.
Verified locally, without root, via PORTAGE_CONFIGROOT with FEATURES=unprivileged:
- four builds (both packages x both USE settings), all exit 0
- cargo test --bin ai-jail: 649 passed, 0 failed, 2 ignored
- both resulting binaries run and report "ai-jail 1.20.2"
- CRATES byte-identical to pycargoebuild output (36 crates, empty diff)
- LICENSE checked against every crate's own Cargo.toml
- cargo audit: 37 crates against 1240 advisories, no vulnerability
- pkgcheck, check-openrc-coverage.sh, check-autoupdate-damage.sh: exit 0
Nothing was merged for real -- this host has no sudo.
Both packages report NonsolvableDepsInDev on musl profiles, from
USE=chromium-launcher depending on www-client/chromium, which ::gentoo masks
on musl. Upstream silences that class of finding by listing the CONSUMER in
profiles/features/musl/package.mask; an overlay cannot, since that directory is
only read when a parent profile points at it and the profiles come from the
master. Masking it repo-wide would break glibc too. The finding is true and
is left standing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: lucascouts <lucascs@protonmail.com>
Date: Mon Sep 7 12:28:34 2026 -0300
fix(sys-apps/{ai-jail,ai-jail-bin}): USE=chromium-launcher installed a menu entry that could not open a window, add(2 patches), rev(1.20.2 -> 1.20.2-r1)
Upstream's dist/desktop/ai-jail-chromium runs
exec ai-jail --browser=soft chromium "$@"
and nothing else. --browser only arranges the isolated browser profile: on
Linux the sandbox still unshares the network namespace and mounts no display
socket, so the launcher produced a Chromium that could neither open a window
nor load a page. Upstream's own README states this -- "--browser alone
produces a browser that cannot load pages -- and on Linux cannot open a window"
-- but the launcher it ships does not act on it.
Measured, not inferred. `ai-jail --dry-run` prints the backend invocation:
--browser=soft -> --unshare-net, no wayland/X11 bind
--browser=soft --network --display -> shared net, /run/user/1000/wayland-0
The previous revision was verified only to the point of installing the two
files in the right places; that it did nothing useful once clicked went
unmeasured. That is the defect this revision fixes, and the reason the check
below now executes the installed script instead of inspecting it.
The patch grants both capabilities and picks the display flag from the session
actually in use -- --display for Wayland, --x11 for X11 -- and fails with a
clear message when neither is present, rather than launching into nothing.
--network is unrestricted network for everything inside the sandbox. For a
browser that is the point; the isolation that remains, and that motivates the
launcher, is the filesystem one: a private tmpfs $HOME, with only
~/.local/share/ai-jail/browser-launcher-cwd writable, so the browser profile
persists and nothing else in $HOME is visible.
Both patches are named without a version. The autoupdate applier renames
ebuilds but never files/, so a versioned patch name would be orphaned on the
next bump and PATCHES would die.
ai-jail-bin now copies the two launcher distfiles into $ in src_unpack
instead of installing them straight from $, so the script goes through
src_prepare like any other source file. The copy, and the patch, are gated on
the flag, since the files are only fetched when it is on.
Verified locally, without root, for both packages and both USE settings:
- four builds, all exit 0; patch applies in each launcher-enabled build
- USE=-chromium-launcher installs no launcher file at all (measured, 0 hits)
- the INSTALLED script, run against a stub ai-jail that echoes its argv:
WAYLAND_DISPLAY set -> ai-jail --browser=soft --network --display chromium
DISPLAY set -> ai-jail --browser=soft --network --x11 chromium
neither set -> diagnostic on stderr, exit 1
and a %U-style URL argument is forwarded intact
- shellcheck -s sh on the patched script: no findings
- pkgcheck on both: only the known musl NonsolvableDepsInDev, unchanged
Nothing was merged for real -- this host has no sudo.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit 6318264191abfac21d6859146c4cc45bf83fd80b
Author: lucascouts <lucascs@protonmail.com>
Date: Mon Sep 7 11:55:52 2026 -0300
add(sys-apps/{ai-jail-1.20.2,ai-jail-bin-1.20.2}), mod(.autoupdate/packages.toml, profiles/use.local.desc): package akitaonrails/ai-jail, source and prebuilt, blocked one-sided
ai-jail runs AI coding agents inside an OS sandbox -- bwrap plus Landlock,
seccomp and rlimits on Linux. GPL-3.0-only, single Rust binary, no daemon and
no systemd unit, so the overlay's OpenRC-coverage rule does not apply.
sys-apps, not app-containers: the Gentoo precedent for process sandboxes is
sys-apps/bubblewrap, sys-apps/firejail and sys-apps/minijail. app-containers
holds runtimes, not wrappers.
Two packages, because they cover different arches. The from-source ebuild is
~amd64 ~arm64; upstream publishes exactly two release assets, linux-x86_64 and
macos-aarch64, so the -bin has no Linux arm64 build to ship and stays
"-* ~amd64". arm64 coverage comes entirely from source. On a bump, read the
ASSET LIST before touching KEYWORDS -- the tag says nothing about it.
The block is weak and declared only in ai-jail-bin, following this overlay's
own precedent in sys-apps/uutils-coreutils-bin. Both install /usr/bin/ai-jail
and cannot coexist, but nothing in the tree RDEPENDs on ai-jail, so portage can
order the swap itself instead of stopping to make the user emerge -C by hand.
USE=chromium-launcher (default off) installs upstream's dist/desktop pair.
Those two files live in the git tree, not in the release tarball, so the -bin
fetches them from raw.githubusercontent.com pinned to the same tag; they are
plain files, hence the custom src_unpack and S="$".
src_prepare deletes "strip = true" from [profile.release]: upstream hands
portage a pre-stripped binary otherwise, which trips the QA check and makes
splitdebug produce empty objects. The sed is guarded by grep -qF so it dies
loudly if upstream drops the line, rather than silently becoming a no-op.
src_test runs the unit tests only. tests/sandbox_escape.rs needs working
unprivileged user namespaces, which the portage sandbox does not provide;
upstream's own PKGBUILD skips them for the same reason.
Verified locally, without root, via PORTAGE_CONFIGROOT with FEATURES=unprivileged:
- four builds (both packages x both USE settings), all exit 0
- cargo test --bin ai-jail: 649 passed, 0 failed, 2 ignored
- both resulting binaries run and report "ai-jail 1.20.2"
- CRATES byte-identical to pycargoebuild output (36 crates, empty diff)
- LICENSE checked against every crate's own Cargo.toml
- cargo audit: 37 crates against 1240 advisories, no vulnerability
- pkgcheck, check-openrc-coverage.sh, check-autoupdate-damage.sh: exit 0
Nothing was merged for real -- this host has no sudo.
Both packages report NonsolvableDepsInDev on musl profiles, from
USE=chromium-launcher depending on www-client/chromium, which ::gentoo masks
on musl. Upstream silences that class of finding by listing the CONSUMER in
profiles/features/musl/package.mask; an overlay cannot, since that directory is
only read when a parent profile points at it and the profiles come from the
master. Masking it repo-wide would break glibc too. The finding is true and
is left standing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>


View
Download
Browse