Fixed
- macOS agents installed by Homebrew can start again. The seatbelt read
list is the counterpart of the Linux base mounts, and it never contained
/opt. Intel Homebrew installs under/usr/local, already reachable through
the/usrgrant, so the gap was invisible on that architecture — but on
Apple Silicon everything Homebrew installs lives under/opt/homebrew, the
sandbox could not see it, andsandbox-execfailed the exec with
execvp() of 'claude' failed: No such file or directorybefore the agent
ever started. Reported by @commonsguy (#120). - An agent may now live anywhere, not just under
$HOME. The read grant
for the invoked command searched only below the home directory. That is right
on Linux, where everything outside the private$HOMEis bind-mounted
regardless, but a seatbelt profile denies by default everywhere, so an agent
under/Applicationsor/nix/storereceived nothing. macOS now resolves the
command the wayexecdoes and grants its symlink chain plus the final
target's directory, wherever that is — still the binary and its install
directory, never the whole prefix. --inherit-envon macOS passed no environment at all. It is documented as
handing the child the entire parent environment, secrets included. Instead the
child got nothing — not evenPATH— because the environment was cleared and
then rebuilt from an empty list. The Linux backend reaches the documented
result by omitting--clearenv; seatbelt now starts from the host environment
and appliesenv_passon top.- In-project maps are no longer refused when the project is reached through a
symlink. The containment check walked past the project root: the root it
compares against is canonicalized and the candidate path is not, so when any
component above the root was a symlink the walk never recognised the root,
continued upward, and rejected on that symlink — the one case it explicitly
treats as irrelevant, since canonicalizing already resolved it. Every map with
a not-yet-existing destination inside the project was refused. This is
permanent on macOS, where$TMPDIRlives under/var→private/var, and
reachable on Linux through a symlinked home. - Every generated macOS profile carried a duplicate read rule, because
/etc
and/private/etcare one directory and the rules are emitted canonically.
Packaging
- The Nix package no longer drags the Rust toolchain into its runtime closure.
It is built with a minimalrustc/cargoinstead of the aggregated
toolchain, and store paths are remapped out of the binary, sorustfmt,
clippy,rust-analyzer,rust-src,rust-docsandrust-stdstop being
runtime dependencies: 47 store paths down to 12. Thanks to @wtf9880 (#116).
Continuous integration
None of the above is a new class of mistake. All of it was invisible because two
checks reported success without checking what they claimed.
- The macOS test suite had no runner. It ran on tag push until v1.18.0 split
the release matrix and left the macOS leg building without testing.
sandbox/seatbelt.rsis#[cfg(target_os = "macos")], so the Linux job only
ever compiled its tests through the cross-target clippy step. Every seatbelt
test written after that was merged unrun.ci.ymlnow runs the suite on
macos-latest; its first run failed 26 tests, and the release workflow's macOS
leg tests before building again, as it used to. nix flake checknever built the package. It realizeschecks.*only,
reporting the package asderivation evaluated to ...ai-jail.drvand stopping
there, so the job added to stop NixOS regressions was verifying thatnixfmt
andtreefmtpass.ci.ymlnow runsnix build .#default, and asserts that
the runtime closure carries no Rust toolchain — the packaging fix above is
otherwise invisible to every check, since reverting it still builds and still
passes every test.
Checksums (SHA256)
31c22e3d5e85afabd63dbe0b662168a8b5b9e77ec455b9c976791a3d2ca4d791 ai-jail-linux-x86_64.tar.gz
72dc94db25ee6100cbc481b5b6f1a8436049bd90a554350a6422a7df912e74e7 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