Skip to content

fix(sandbox): install bubblewrap/socat + probe so bash isolation actually engages#58

Merged
Anton-Horn merged 2 commits into
mainfrom
fix/bash-sandbox-deps
Jun 9, 2026
Merged

fix(sandbox): install bubblewrap/socat + probe so bash isolation actually engages#58
Anton-Horn merged 2 commits into
mainfrom
fix/bash-sandbox-deps

Conversation

@Anton-Horn

Copy link
Copy Markdown
Contributor

Problem

The bash tool is supposed to be confined to its project dir via @anthropic-ai/sandbox-runtime (bubblewrap on Linux) — denyRead covers the whole projects root, allowRead re-allows only the current project. But the image never installed bwrap/socat, so on Linux SandboxManager.initialize() threw (bubblewrap (bwrap) not installed), the extension caught it, and bash silently ran unsandboxed in production.

Verified on zero-server:

bwrap MISSING   socat MISSING   rg /usr/bin/rg
$ cat /app/data/projects/*/.pi/SYSTEM.md   →  sibling project readable

So a prompt-injected/buggy bash command could read or write any sibling project under /app/data/projects/<id>. (The in-process read/ls/grep/find/write/edit tools stay safe — they enforce project scope with JS path checks — so the gap was bash-specific.)

Changes (this repo)

  • Dockerfile: install bubblewrap + socat (ripgrep already present).
  • Runtime probe (sandboxActuallyRuns): initialize() only which-checks the binaries, but a present bwrap still fails if the container forbids user/mount namespaces. We wrap a trivial command and run it; only trust the sandbox if it exits cleanly. Without this, a deploy that ships bwrap but blocks userns would break every bash command instead of falling back.
  • Loud logging: sandbox-unavailable now logs via the server logger at error (was a silent ctx.ui.notify), so a fail-open can't go unnoticed.
  • PI_REQUIRE_BASH_SANDBOX=1: makes bash fail closed (refused) when the sandbox can't engage. Flip it on once the deploy is verified so a regression can't silently re-open the hole.

⚠️ Platform follow-up (required, not in this repo)

bubblewrap needs user/mount namespaces, which the container currently can't create (CapEff=0, seccomp blocks unshare). The .ocd-deploy.json manifest has no field for seccomp/cap_add/privileged, so this must be granted by how the OCD platform launches the container — e.g. --security-opt seccomp=unconfined (or CAP_SYS_ADMIN / userns).

Until that lands, the probe fails and bash falls back to unsandboxed (now loudly logged) — same behavior as today, minus the silence. Once the platform grants the privilege and a redeploy confirms 🔒 Bash sandboxed, set PI_REQUIRE_BASH_SANDBOX=1.

Safety of merging now

Auto-deploy will rebuild with bwrap/socat present. The probe will fail (no userns yet) → bash falls back to unsandboxed exactly as before, but with a loud error log. No regression to bash behavior.

…ally engages

The bash tool is meant to be confined to its project dir via
@anthropic-ai/sandbox-runtime (bubblewrap on Linux), blocking
cross-project reads. But the image never installed bwrap/socat, so
SandboxManager.initialize() threw on the missing deps, the extension
caught it, and bash silently ran UNSANDBOXED in production — any
prompt-injected command could read/write sibling projects under
/app/data/projects/<id> (the in-process read/ls/grep/find tools stay
safe via JS path checks; the gap was bash-specific).

- Dockerfile: install bubblewrap + socat (ripgrep already present).
- Add a runtime probe (sandboxActuallyRuns): initialize() only
  which-checks the binaries, but a present bwrap still fails if the
  container forbids user/mount namespaces. Wrap a trivial command and
  run it; only trust the sandbox if it exits cleanly. Without this, a
  deploy that ships bwrap but blocks userns would break *every* bash
  command instead of falling back.
- Log sandbox-unavailable loudly via the server logger (was a silent
  ctx.ui.notify), so a fail-open can't go unnoticed.
- PI_REQUIRE_BASH_SANDBOX=1 makes bash fail *closed* (refused) when the
  sandbox can't engage — flip it on once the deploy is verified so a
  regression can't silently re-open the hole.

PLATFORM FOLLOW-UP (not expressible in .ocd-deploy.json): bubblewrap
needs the container launched with --security-opt seccomp=unconfined
(or CAP_SYS_ADMIN / userns). Until OCD grants that, the probe fails and
bash falls back to unsandboxed (now loudly logged).
Pairs with the OCD per-app userns flag — launches the zero-server
container with --security-opt=seccomp=unconfined so the agent's bash
sandbox (bubblewrap) can create user/mount namespaces.
@Anton-Horn Anton-Horn merged commit 30e1237 into main Jun 9, 2026
1 check passed
@Anton-Horn Anton-Horn deleted the fix/bash-sandbox-deps branch June 9, 2026 14:04
Anton-Horn added a commit that referenced this pull request Jun 9, 2026
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