Repository navigation
feat(sandbox): seccomp syscall allowlist for the bubblewrap backend - #365
Merged
Merged
Conversation
The Linux sandbox isolated the guest with namespaces only; a kernel bug
reachable from inside was a full host escape. bwrap now installs a
classic-BPF seccomp program, generated on the host at every start from
a strict allowlist (Docker's default profile minus capability-gated
entries), before it execs the guest.
- src/seccomp/policy.ts: allowlist, kill set (bpf, perf_event_open,
userfaultfd, module loading, kexec, key syscalls), documented deny
list, socket-domain and clone/unshare flag rules.
- src/seccomp/bpf.ts: cBPF assembler with labels; arch + x32 gate,
argument checks, chunked allowlist, EPERM/ENOSYS default deny,
audit mode via SECCOMP_RET_LOG.
- src/seccomp/syscalls-{x64,arm64}.ts generated from the kernel uapi
headers by script/gen-syscall-tables.ts.
- bwrap/args.ts gains seccompFd -> --seccomp; bwrap/provider.ts writes
runtime/seccomp.bpf and inherits it as stdio slot 3; per-sandbox
`seccomp` config field; desktop reads CODEV_SANDBOX_SECCOMP for the
machine-wide default (enforce).
- Tests: a cBPF interpreter runs the real program bytes for every
syscall number on both arches; real-bwrap integration tests show
ordinary work passing, mount refused, unshare -U killed.
- Docs: README, docs/seccomp.md "What shipped", SECURITY.md scoping
(+ DIVERGENCES row), CHANGELOG.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0124etPWktHa6gqvcXrjcCm4
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.
Summary
The Linux desktop sandbox (
bwrap) isolated the guest with namespaces only. Namespaces control what the guest can see; they do nothing to shrink what it can say to the shared host kernel, so a single kernel LPE reachable from inside the guest was a full host escape with no second wall.docs/seccomp.mdplanned the fix; this PR implements it.bwrap now installs a seccomp BPF program, generated on the host at every sandbox start, before it execs the guest:
bpf,perf_event_open,userfaultfd,init_module/finit_module/delete_module,kexec_*,keyctl/add_key/request_key.clone/unsharewithCLONE_NEWUSERis fatal, any otherCLONE_NEW*is refused with EPERM;socket/socketpairlimited toAF_UNIX,AF_INET,AF_INET6andAF_NETLINK+NETLINK_ROUTE(getifaddrs), everything else EAFNOSUPPORT.archand x32-ABI numbers are killed before any number is compared.clone3→clone,faccessat2→faccessat) keep working.io_uring_*andclone3are deliberately unlisted.enforce(default),audit(every non-allow verdict becomesSECCOMP_RET_LOG: kernel logs it, nothing is blocked),off. Machine-wide default viaCODEV_SANDBOX_SECCOMP; a persisted per-sandboxseccompfield wins over it. No UI yet.Departures from
docs/seccomp.mdEach is recorded in a new "What shipped" section of that doc.
src/seccomp/bpf.ts), not precompiled with libseccomp in CI and shipped via the image manifest. That makes the PR self-contained (no Rust tool, no storage publish). The arm64-correctness concern is met by generating the syscall tables straight from the kernel uapi headers (script/gen-syscall-tables.ts→syscalls-{x64,arm64}.ts) and by tests that run the real program bytes through a cBPF interpreter for every syscall number on both arches. Trade-off: the policy versions with the app, not with the rootfs image.--seccomp, not--add-seccomp-fd: one program, and the older flag is accepted by every bwrap release, which removes the degradation question.ptrace→ EPERM, not kill: strace/gdb should fail with a message, not vanish.AF_INET/AF_INET6allowed: the guest's own server, the broker stub, and any dev server the agent starts are loopback TCP;--unshare-netalready leaves nothing else reachable.enforce, not audit-first: with no fleet to collect audit logs from, an audit default would have shipped no protection.audit/offremain the escape hatches.Verification
bun testinpackages/codev-sandbox: 194 pass. Includes a cBPF interpreter test that checks the real program bytes (allowlist passes, kill/deny sets don't, arch/x32 gate, clone/unshare flags, socket domains, audit mode, every nr 0–1023 terminates within the kernel instruction limit), and real-bwrap integration tests on this host (bwrap 0.6.1): ordinary work passes,mountis refused,unshare -Uexits 159 (SIGSYS).enforcewith the host's Node 24, git and python3: loopback HTTP server + fetch,child_process,worker_threads,fs.watch,os.networkInterfaces(),dns.lookup,git init/commit, pythonAF_INETbind all work; pythonAF_PACKETgetsEAFNOSUPPORT.bun typecheckclean inpackages/codev-sandboxandpackages/desktop.nodeimporting the bundle and callingServer.listen), but a first real Work session on Linux is the remaining check.Docs / bookkeeping
packages/codev-sandbox/README.md: newseccomp/bullet, provider/args notes, two Known-gaps entries (no audit phase yet; WSL has no filter).SECURITY.md: "No Sandbox" scoped to CLI/local sessions; new "Desktop sandbox (Work sessions)" section puts escapes from the desktop sandbox in scope. Row added toDIVERGENCES.md.CHANGELOG.md: Sandbox entry under Unreleased.🤖 Generated with Claude Code
https://claude.ai/code/session_0124etPWktHa6gqvcXrjcCm4