feat(sandbox): bind /proc in bwrap on Lakebox hosts#3258
Conversation
The linux_bwrap sandbox mounts a fresh procfs under --unshare-pid, but a Lakebox microVM masks /proc so that mount returns EPERM and the sandbox fails to start. That blocked linux_bwrap — and the L7 egress management built on top of it — on the Lakebox backend. Bind the existing /proc instead of mounting a fresh one, but only on outer sandbox backends known to be safe for it (allow-list: lakebox). The backend is read from OMNIGENT_HOST_SANDBOX_BACKEND when set, else autodetected via the /run/lakebox marker. Everywhere else the fresh-proc mount and its fail-closed behavior stay unchanged. Binding /proc exposes the outer process list and world-readable per-proc files (cmdline/comm/stat/status). The retained user namespace still blocks ptrace-gated files (environ/mem/maps/fd) and --unshare-pid still contains signalling, so the leak is acceptable on a single-tenant Lakebox microVM. Signed-off-by: Thomas Garnier <6202935+mxatone@users.noreply.github.com>
fc25813 to
f2b8935
Compare
| try: | ||
| if _LAKEBOX_MARKER.is_dir(): | ||
| return "lakebox" | ||
| except OSError: |
|
|
@mxatone This PR is a Bug fix, Feature, or UI / frontend change but the Demo section is missing or only contains a placeholder. These change types require a screenshot or screen recording so reviewers can see the new behaviour without checking out the branch. Please update the Demo section with:
Use |
Related issue
N/A
Summary
The
linux_bwrapsandbox mounts a fresh procfs under--unshare-pid. ADatabricks Lakebox microVM masks
/proc, so that mount is rejected withbwrap: Can't mount proc on /newroot/proc: Operation not permittedand thesandbox never starts. That blocked
linux_bwrap— and the L7 egressmanagement built on top of it — on the Lakebox backend.
/procinstead of mounting a fresh one, but only onouter sandbox backends known to be safe (allow-list:
lakebox).wrap_launcher_argvemits--bind /proc /procin that case and--proc /proceverywhere else.OMNIGENT_HOST_SANDBOX_BACKENDwhen alauncher sets it, otherwise autodetect Lakebox via the
/run/lakeboxmarker. Detection runs in the host process, never inside the sandboxed
agent, so it cannot be forged from within the sandbox.
its fail-closed behavior. Adding a backend later is one entry in
_PROC_BIND_HOST_BACKENDS.ELI5. bwrap normally gives the agent a brand-new
/procthat shows onlyits own processes. Lakebox does not allow creating that new
/proc, so theagent could not start at all. On Lakebox we hand the agent the existing
/procinstead. That reveals the list of other process names on the box,but the user namespace still hides the sensitive parts (each process's
environment, memory, and open files), so it is a safe trade on a
single-tenant dev VM.
flowchart TD A[bwrap builds sandbox argv] --> B{host backend on the<br/>proc-bind allow-list?} B -- "lakebox (env var or /run/lakebox)" --> C["--bind /proc /proc<br/>reuse host proc"] B -- "anything else" --> D["--proc /proc<br/>fresh procfs, fail-closed"]Security trade-off (documented inline in
bwrap_sandbox.py): binding/procexposes the outer process list and the world-readable per-processfiles (
cmdline/comm/stat/status). It does not expose theptrace-gated files (
environ/mem/maps/fd): the retained usernamespace keeps those blocked even for same-uid targets and even as
namespaced root, and
--unshare-pidstill contains signalling. Because thebind is a security downgrade, it is gated to an explicit allow-list rather
than a blind "fresh-proc failed, retry with a bind" fallback.
Test Plan
Unit tests and lint (local):
uv run pytest tests/inner/test_bwrap_sandbox.py -q -k "proc or host_backend"→ 7 passed.uv run ruff checkandruff format --checkon both files → clean;pre-commiton both files → all hooks passed.Live end-to-end on a Lakebox microVM (
e2-dogfood,omnigent-egress-test):detect = lakebox | should_bind_proc = True.linux_bwrapshell now starts and runs:idanduname -areturnrc=0;cat /proc/1/cmdlinereturns the hostsandbox-daemon, proving/procis the bound host proc (a fresh procfs would show the helper asPID 1).
OMNIGENT_HOST_SANDBOX_BACKEND=bare→should_bind_proc = False→ reproduces the original failure
bwrap: Can't mount proc on /newroot/proc: Operation not permitted, confirming the gate is precisely scoped.linux_bwrap+egress_rules: ["* pypi.org/**"](default-deny): allowedpypi.org→HTTP=200 exit=0; deniedexample.com→HTTP=000 exit=56(blocked by the egress proxy).
New coverage (unit):
_detect_host_sandbox_backend— none without signals, env declarationnormalised and authoritative, non-lakebox env keeps fresh proc even with
the marker present,
/run/lakeboxmarker autodetects lakebox.wrap_launcher_argv— emits--proc /procby default and--bind /proc /procon lakebox (with/devand/tmpunchanged).Pre-existing, unrelated: the four
resolve()tests intests/inner/test_bwrap_sandbox.pyfail on macOS with "only available onLinux". I confirmed via
git stashthat they fail identically without thischange (the Linux gate is in the base file), so they are not a regression;
they pass on Linux/CI.
Demo
N/A (non-visual change).
Type of change
Test coverage
Coverage notes
Manual verification was run on a live Lakebox microVM on
e2-dogfood: thebranch's
bwrap_sandbox.pywas loaded in the VM and exercised viacreate_os_environment().shell()for the positive (linux_bwrapstarts,/procbound), negative-control (forced non-lakebox backend reproduces themount
EPERM), and egress (pypi.orgallowed,example.comdenied) cases.The VM's file was restored to its original state afterward. The proc-mode
selection logic itself is covered by the new unit tests above.
Changelog
linux_bwrapsandboxes and their egress rules now run on the Databricks Lakebox backend.