feat(sandbox): kernel filesystem boundary for agent containment (DPLAN-0202/FPLAN-0250)

Every autonomous agent can launch inside a kernel-enforced mount namespace
(srt -> bwrap+seccomp): reads stay open (shared live FS preserved, bind-mount not
isolation; own-tree writes land live), but rm/python/find/Write on .git or sibling
trees hit EROFS. /tmp + own tree writable; .git RW devpulse, RO builders. Inert by
default behind AIPASS_SANDBOX_ENABLED (off); flag-off path byte-identical to old.

hooks: srt wrapper + per-role build_policy + broker_secret mask; rm_gate demoted.
drone: out-of-sandbox broker (identity allowlist, openat2 RESOLVE_BENEATH, HMAC
handshake over inherited fd, audit); drone rm via broker when sandboxed.
ai_mail: dispatch gate + broker-fd wiring (fail-loud exit -4, never silent).
aipass: doctor Sandbox group + setup.sh prereqs (LOUD on missing).

Proven by a live 16-check red-team suite. seedgo 100% + 2859 tests green across
all 5 touched branches.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
AIOSAI
2026-06-10 13:16:22 -07:00
co-authored by Claude Opus 4.8
parent 0c6e8ac425
commit 0b4ba63fae
29 changed files with 5448 additions and 245 deletions
+86
View File
@@ -226,6 +226,92 @@ if [ "$IS_WINDOWS" -eq 1 ]; then
fi
fi
# --- Sandbox prerequisites (kernel FS boundary) ---
echo ""
echo "Checking sandbox prerequisites ..."
if [ "$IS_WINDOWS" -eq 1 ] || [ "$IS_MACOS" -eq 1 ]; then
echo " kernel sandbox: Linux-only for now, skipping"
else
SB_MISSING=()
# bwrap
if command -v bwrap &>/dev/null; then
echo " bwrap ... $(bwrap --version 2>/dev/null || echo 'found')"
else
echo " bwrap ... MISSING"
echo " sudo apt install bubblewrap"
SB_MISSING+=("bwrap")
fi
# node
if command -v node &>/dev/null; then
echo " node ... $(node --version 2>/dev/null)"
else
echo " node ... MISSING"
echo " Install Node.js: https://nodejs.org/"
SB_MISSING+=("node")
fi
# npm (needed for srt install)
if command -v npm &>/dev/null; then
echo " npm ... $(npm --version 2>/dev/null)"
else
echo " npm ... MISSING"
SB_MISSING+=("npm")
fi
# @anthropic-ai/sandbox-runtime — resolve same way as _srt_resolve.mjs
if command -v node &>/dev/null; then
SRT_PATH=$(node -e "
const p = require('path');
const fs = require('fs');
const prefix = p.dirname(p.dirname(process.execPath));
const entry = p.join(prefix, 'lib/node_modules/@anthropic-ai/sandbox-runtime/dist/index.js');
if (fs.existsSync(entry)) process.stdout.write(entry);
else process.exit(1);
" 2>/dev/null) || SRT_PATH=""
if [ -n "$SRT_PATH" ]; then
echo " srt ... $SRT_PATH"
else
echo " srt ... MISSING"
if command -v npm &>/dev/null; then
echo " Attempting: npm install -g @anthropic-ai/sandbox-runtime"
if npm install -g @anthropic-ai/sandbox-runtime 2>/dev/null; then
echo " srt ... installed"
else
echo " Install failed (may need sudo). Run manually:"
echo " sudo npm install -g @anthropic-ai/sandbox-runtime"
SB_MISSING+=("srt")
fi
else
echo " Install node+npm first, then: npm install -g @anthropic-ai/sandbox-runtime"
SB_MISSING+=("srt")
fi
fi
else
echo " srt ... skipped (no node)"
SB_MISSING+=("srt")
fi
# rg (ripgrep)
if command -v rg &>/dev/null; then
echo " rg ... $(rg --version 2>/dev/null | head -1)"
elif [ -f "$HOME/.local/bin/rg" ]; then
echo " rg ... $HOME/.local/bin/rg"
else
echo " rg ... MISSING"
echo " sudo apt install ripgrep"
SB_MISSING+=("rg")
fi
if [ ${#SB_MISSING[@]} -eq 0 ]; then
echo " sandbox prereqs: READY"
else
echo " sandbox prereqs: INCOMPLETE (${SB_MISSING[*]} missing) — aipass doctor for details"
fi
fi
# --- Verify CLI entry points ---
FAIL=0