Skip to content

feat(launch): start the agent inside ai-jail's sandbox with --jail - #30

Merged
protonspy merged 1 commit into
mainfrom
feat/jail
Aug 27, 2026
Merged

feat(launch): start the agent inside ai-jail's sandbox with --jail#30
protonspy merged 1 commit into
mainfrom
feat/jail

Conversation

@protonspy

Copy link
Copy Markdown
Owner

An agent needs filesystem access to do its job, and the same access lets it run rm -rf, read ~/.aws, or ship a private key somewhere — by accident, on an instruction planted in a file it was asked to read, or through a dependency it installed. autonomy: auto sharpens that rather than softening it: nobody is watching the step where noticing was still possible, which is the argument for a boundary around the whole session instead of around each decision inside it.

scc launch --jail starts the harness inside ai-jail — bubblewrap on Linux, sandbox-exec on macOS — wrapping outermost, Headroom included.

scc launch claude --jail
scc launch claude --jail --jail-arg --lockdown

It refuses rather than degrading

This is the one integration here that does. Headroom, CodeGraph and RTK all end in the agent starting anyway when their binary is missing, because each of the three is an enhancement, and failing a launch over a compression proxy would put scc's preference above the thing the user actually asked for.

A sandbox inverts that. It is the thing asked for, by name, and the failure mode is not a worse session — it is somebody who typed --jail, watched an agent start, and believes they are contained. A false belief about containment is worse than a known absence of it, because with the known absence they would not have run the thing at all.

So a missing binary, a declined install, an unattended run, or a platform with no backend all end in nothing starting — and the refusal is decided before the graph is built or the entry file is touched, so a launch that cannot be jailed leaves the workspace exactly as it found it. There is a test for each half.

scc decides two flags and no more

ai-jail defaults network and credential state to off, which is the right default for a sandbox and the wrong one for a launcher: an agent with neither cannot reach the model it is or authenticate as anyone, so ai-jail claude bare is a jail that starts nothing. Those two are function.

Everything else — lockdown, denied paths, extra mounts, Docker, the browser — is policy, and policy belongs in ai-jail's own ~/.ai-jail and ./.ai-jail, which it reads by itself and scc never writes. A launcher that quietly loosened somebody's sandbox policy would be the worst kind of helpful. --jail-arg is the per-run escape hatch and comes last on the command line, so a flag the user typed wins over the two scc supplies.

Even those two are read off ai-jail --help rather than compiled in — the lesson internal/headroom already paid for, where a hardcoded flag name would have turned somebody else's rename into a launch that dies on no such option. A build advertising neither gets no substitute, only a warning: a sandbox opened by a guess is precisely the failure this feature exists to prevent.

Integrating the binary, not the script

The article that started this ships a bwrap shell script, and copying it into Go was the obvious move and the wrong one. The tool does namespaces, Landlock and seccomp; it has since grown a second platform backend; and a half-copy of a sandbox is worse than none for the same reason the refusal above exists. internal/jail is the fifth integration package, alongside rtk, headroom, codegraph and git — it composes an argument vector and reads a --help, and that is all.

Windows has no backend and is unlikely to get one, since there is nothing there for bubblewrap or sandbox-exec to stand on. WSL2 is the answer and it is a real one rather than a workaround — scc inside WSL2 is scc on Linux. The refusal names it.

Credit

The idea and the tool are Fábio Akita'sakitaonrails/ai-jail.

Checks

No template changed, so no template version bump. gofmt -l clean · go vet ./... clean · go test ./... green · golangci-lint run → 0 issues. The refusal tests skip if ai-jail happens to be installed on the runner; the argument-vector and flag-discovery tests are pure and run everywhere.

An agent needs filesystem access to do its job, and the same access lets it run
`rm -rf`, read ~/.aws, or ship a private key somewhere — by accident, on an
instruction planted in a file it was asked to read, or through a dependency it
installed. `autonomy: auto` sharpens that rather than softening it: nobody is
watching the step where noticing was still possible, which is the argument for a
boundary around the whole session instead of around each decision inside it.

`scc launch --jail` starts the harness inside ai-jail — bubblewrap on Linux,
sandbox-exec on macOS — wrapping outermost, Headroom included.

**It refuses rather than degrading, and that is the design.** Headroom, CodeGraph
and RTK all end in the agent starting anyway when their binary is missing, because
each is an enhancement and failing over a compression proxy would put scc's
preference above what the user asked for. A sandbox inverts that: it *is* what was
asked for, by name, and the failure mode is not a worse session but somebody who
typed --jail, watched an agent start, and believes they are contained. A false
belief about containment is worse than a known absence of it — with the absence
they would not have run the thing at all. So a missing binary, a declined install,
an unattended run, or a platform with no backend all end in nothing starting, and
the refusal is decided before the graph is built or the entry file is touched.

**scc decides two flags and no more.** ai-jail defaults network and credential
state to off, which is right for a sandbox and wrong for a launcher: an agent with
neither cannot reach its model or authenticate, so `ai-jail claude` bare is a jail
that starts nothing. Those two are function. Everything else — lockdown, denied
paths, extra mounts, Docker, the browser — is policy, and policy lives in ai-jail's
own ~/.ai-jail and ./.ai-jail, which it reads by itself and scc never writes.
--jail-arg is the per-run escape hatch and comes last, so a flag the user typed
wins. Even those two are read off `ai-jail --help` rather than compiled in — the
lesson internal/headroom already paid for — and a build advertising neither gets no
substitute, only a warning: a sandbox opened by a guess is the failure this feature
exists to prevent.

internal/jail is the fifth integration package. It composes a command line and
reimplements nothing: the tool does namespaces, Landlock and seccomp, and a
half-copy of a sandbox is worse than none for the same reason the refusal exists.
Windows has no backend and is unlikely to get one, since there is nothing there for
bubblewrap or sandbox-exec to stand on; WSL2 is the answer, and scc inside WSL2 is
scc on Linux.

The idea and the tool are Fábio Akita's:
https://akitaonrails.com/2026/01/10/ai-agents-garantindo-a-protecao-do-seu-sistema/
https://github.com/akitaonrails/ai-jail
@protonspy
protonspy merged commit d666176 into main Aug 27, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant