Part III — The decision (SB7–SB9)¶
SB7 — is_sandbox_capable: the capability matrix ✅¶
is_sandbox_capable(framework_id, platform) is the single platform-aware decision function. Its matrix:
| Platform | Framework | Capable? | Boundary |
|---|---|---|---|
| Linux | any | ✅ | the launcher's bwrap branch (framework-neutral; VERIFIED) |
| macOS | any | ✅ | claude/goose via their native Seatbelt; every other framework via the launcher's build_macos (sandbox-exec) branch (UNVERIFIED until mac-escape-tests.sh passes) |
| any (incl. Windows) | claude |
✅ | Claude Code's own native sandbox |
| Windows / any other | non-claude | ❌ | no emittable boundary |
As of 2026-W36 the matrix is framework-neutral on both POSIX platforms — the SAME launcher carries a
bwrap branch (Linux) and a sandbox-exec/build_macos branch (macOS). SANDBOX_CAPABLE_FRAMEWORKS is
a convenience set sampling only ("claude","goose") for the current host; the authoritative answer is
is_sandbox_capable itself.
Source: agentteams/host_features.py:222 is_sandbox_capable.
The decision graph (G2)¶
flowchart TD
Q["confinement requested?<br/>(profile confined/exclusive OR *:sandbox)"] -->|no| COOP["cooperative:<br/>no boundary (SB3)"]
Q -->|yes| P{"platform?"}
P -->|linux| FW{"framework == claude?"}
FW -->|yes| CNL["claude: native settings-block sandbox (SB10),<br/>No advisory. ALSO emits the launcher (SB12/SB13),<br/>but native is claude's intended boundary — on Linux<br/>that native arm is VERIFIED only with the documented<br/>bwrap AppArmor profile on Ubuntu (SB20)"]
FW -->|no| ML["manual-wire advisory (NON-FATAL)<br/>+ emit bwrap launcher (SB12)"]
P -->|darwin| DF{"framework?"}
DF -->|claude| CN["claude: native settings-block<br/>sandbox (SB10). No advisory."]
DF -->|goose| SB["goose: native Seatbelt profile +<br/>inert config example (SB11). No advisory."]
DF -->|"other (codex/copilot/agents-md)"| MM["macOS manual-wire advisory (NON-FATAL, SB8)<br/>+ emit launcher build_macos branch (SB12);<br/>UNVERIFIED until mac-escape-tests (SB20)"]
P -->|win32/other| W2{"framework == claude?"}
W2 -->|yes| CW["claude: native (product-arm<br/>unverified on Windows)"]
W2 -->|no| UH["unenforced-host advisory (FATAL, SB8)"]
SB8 — Three advisories: one fatal, two non-fatal ✅¶
privilege_profile_advisory returns one of three codes, or None:
privilege-profile-unenforced-host(FATAL) — no boundary is emittable here: Windows (and any non-claude framework on another non-POSIX target). Since 2026-W36 macOS is emittable framework-neutrally, so a confined codex/copilot/agents-md team on a mac is NO LONGER fatal (see the macOS manual-wire code below).resolve_host_features_and_adviseraisesPrivilegeConfinementError(fail-closed) unless--allow-unenforced-confinement, which downgrades it to a persisted warning and emits no boundary on that host.privilege-profile-linux-launcher-manual-wire(NON-FATAL) — on Linux, every framework except claude: enforcement IS available (the launcherbwrapbranch) but is not auto-applied; the operator must WRAP the invocation. Never fail-closes; always warns + persists.privilege-profile-macos-launcher-manual-wire(NON-FATAL, 2026-W36) — on macOS, every framework except claude AND goose (both have their own auto-applied native macOS boundary, SB11). The launcher'sbuild_macosbranch is emitted (sandbox-exec+ Seatbelt profile, RLIMIT_CPU/NPROC caps, loopback-only proxy DNS contract, non-exhaustive setuid-exec denylist). Also not auto-applied (WRAP the invocation); never fail-closes. Its message carries the honest macOS residuals (memory UNCAPPED, no syscall filtering, setuid denylist ≠ NoNewPrivs, loopback-only proxy) and the enforcement-UNVERIFIED gate (SB20).None— a boundary wired through the framework's own config surfaces no extra advisory: claude native everywhere; goose Seatbelt on macOS. Claude/Linux caveat: claude's native arm is its intended boundary and is VERIFIED on Linux only under a precondition (Ubuntu needs Claude Code's documented bwrap AppArmor profile, SB20) while the verified launcher rides along un-advised — "no advisory for claude" ≠ "fully covered."
resolve_host_features_and_advise fail-closes on the fatal code only (fatal = code ==
"privilege-profile-unenforced-host"); both manual-wire codes warn + persist, never raise. Why the
manual-wire codes exist (a closed gap): making Linux — then macOS — capable for every framework would
otherwise suppress the old unenforced-host advisory for codex/copilot/agents-md, whose launcher is inert
until wrapped. The non-fatal advisories restore the honest signal, per POSIX platform.
Source: agentteams/host_features.py:325 privilege_profile_advisory, :376 (unenforced-host),
:404 (linux manual-wire), :426 (macos manual-wire); agentteams/cli/artifacts.py:330
resolve_host_features_and_advise, :381.
SB9 — Fail-closed by default on generate ✅¶
On the interactive generate path a fatal (unenforceable) confinement request refuses to emit an
inert boundary rather than ship a config that looks protective — the fail-safe-defaults principle.
--allow-unenforced-confinement opts into proceeding with the advisory instead (and emits no boundary
on that host). The --convert-from/--fleet/render paths keep an advisory-not-raise default, so an
unenforceable target there degrades to a warning rather than a non-zero exit.
Source: agentteams/cli/artifacts.py:381; agentteams/frameworks/_sandbox_emit.py:116.
Next: Part IV — The mechanisms.