Isolate the agent bash tool with Landlock on Linux#60
Merged
Conversation
bubblewrap (sandbox-runtime's Linux backend) can't engage on the hardened OCD deploy — capless root + blocked unprivileged userns — so bash silently ran unsandboxed and a prompt-injected command could read/write sibling projects. Landlock fits the constraints: it needs no caps and no userns, only PR_SET_NO_NEW_PRIVS (already set on the container). Verified on the prod box: kernel 6.8, Landlock ABI v4 usable (not seccomp-blocked), and a real contained bash reads/writes its own project but is denied read/write/ list on siblings. - server/landlock-exec/zero-landlock.c: self-contained C helper (UAPI structs inline, no kernel-header dep). Applies a deny-by-default Landlock ruleset from --rw/--ro/--rwfile flags then execs the command; fail-closed. - server/Dockerfile: compile it to /usr/local/bin/zero-landlock; mkdir /var/empty. - project-sandbox: on Linux, probe `zero-landlock --check` at session_start and route bash through it (projects root never granted -> siblings denied by default); macOS keeps sandbox-exec; unavailable -> unsandboxed fallback. Network untouched, so the zero CLI -> loopback proxy keeps working. Also closes two related gaps surfaced while doing this: - Scrub the server's own secrets (JWT_SECRET, CREDENTIALS_KEY, OPENROUTER_API_KEY, BRAVE_SEARCH_API_KEY, TELEGRAM_WEBHOOK_SECRET, plus a secret-shaped name pattern) from the bash child env. The zero CLI reaches models/search via the in-process proxy, so it never needs them. - Relocate browser storageState (.chrome-state.json) out of the project dir to a sibling root (chromeStateFileFor), since Landlock grants the whole project dir rw and can't do per-file deny. Migrated on next browser open so existing sessions aren't logged out. Drops the now-redundant in-process deny machinery; keeps the snapshot exclude for un-migrated stragglers.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
bubblewrap (the Linux backend of
@anthropic-ai/sandbox-runtime) can't engage on the hardened OCD deploy — the container is capless root (CapEff=0) and the host blocks unprivileged user namespaces — so the agent'sbashsilently fell back to running unsandboxed. A prompt-injected or buggy command could read/write sibling projects under the projects root. (PRs #58/#59 confirmed bubblewrap is a dead end here.)Landlock is the one FS-sandbox primitive that fits: it needs no capabilities and no userns, only
PR_SET_NO_NEW_PRIVS— which the container already sets. Networking is untouched, so thezeroCLI ↔ loopback proxy keeps working (the thing that forced us off bubblewrap's--unshare-net).Verified on the prod box (
ocd ssh zero-server)NoNewPrivs=1→ Landlock ABI v4 usable (not seccomp-blocked:create_ruleset/add_rule/restrict_selfall succeed).bashthat reads/writes its own project ✓ and is denied read + write + evenlsof a sibling project ✓ — while staying a functional shell.What changed
server/landlock-exec/zero-landlock.c— self-contained C helper (UAPI structs inline, no kernel-header dep). Applies a deny-by-default Landlock ruleset from--rw/--ro/--rwfileflags, thenexecvps the command. Fail-closed;--checkmode for probing.server/Dockerfile— compiles it to/usr/local/bin/zero-landlock, creates/var/empty.project-sandbox/index.ts— on Linux, probeszero-landlock --checkatsession_startand routes bash through it (--rw <projectDir> --rw /tmp --ro <system+pkg roots> --rwfile /dev/* -- bash -c <cmd>). The projects root is never granted, so siblings are denied by allowlist default. macOS keeps sandbox-exec; if Landlock is unavailable it falls back to unsandboxed with a notify. Covers parent turns and subagents (shared factory).Related gaps closed while here
JWT_SECRET,CREDENTIALS_KEY,OPENROUTER_API_KEY,BRAVE_SEARCH_API_KEY,TELEGRAM_WEBHOOK_SECRET(+ a secret-shaped name pattern) from the bash child env. ThezeroCLI reaches models/search via the in-process proxy, so it never needs them. The per-turn proxy token is re-added after the scrub..chrome-state.json— browser storageState (cookies/localStorage/IndexedDB) moves out of the project dir to a sibling root (chromeStateFileFor), since Landlock grants the whole project dir rw and can't do per-file deny. Migrated on next browser open so existing sessions aren't logged out. Removes the now-redundant in-process deny machinery; keeps the snapshot exclude for un-migrated stragglers.Notes / scope
tsc --noEmitclean for project source (pre-existing s3litenode_moduleserrors unrelated).scripts/landlock-probe.pyis included as a standalone diagnostic.🤖 Generated with Claude Code