Skip to content

docs(adr-0018): propose the sandboxed-host shell engine + phased development plan (#194)#195

Closed
hartsock wants to merge 1 commit into
mainfrom
issue-194/adr-0018-sandboxed-host-engine
Closed

docs(adr-0018): propose the sandboxed-host shell engine + phased development plan (#194)#195
hartsock wants to merge 1 commit into
mainfrom
issue-194/adr-0018-sandboxed-host-engine

Conversation

@hartsock

@hartsock hartsock commented Jul 3, 2026

Copy link
Copy Markdown
Member

What this PR does

Adds ADR 0018 (Status: Proposed) — the decision record and phased development plan for #194's Option C: a sandboxed-host shell engine behind the ADR 0005 D2 seam.

The engine spawns a real POSIX shell (/bin/sh -c) as the root of the confined process tree with zero L2 enforcement — dynamic constructs ($(...), backticks, pipes, redirects) run because the kernel, not the parser, bounds their reach. Every restricted axis is either held by an L3 backend at the grain that backend honestly claims, or the engine refuses (reusing confinement_unenforceable / the ADR 0012 floor verbatim). Highlights:

  • Engages the post-0005 L3 line directly: Seatbelt exec → Kernel (ADR 0014) means the engine can serve restricted-exec grants on macOS; Landlock gets ADR 0011 boundary grain; net gets the ADR 0015 fence + ADR 0016 proxy through the env seam; Tier-2 identity (ADR 0013 rootfs) is a later optional phase.
  • Honesty is preserved in both directions: where the subset engine's interceptor would be the stronger real posture, the host engine refuses — no request ever gets a weaker real posture by switching engines. Where the jail holds everything at kernel grain, the engine is stronger than an interceptor (which is blind to a permitted child's interior).
  • Selection is an ADR 0017 mechanism knob (shell.engine = "subset" | "host"), never authority; defaults reproduce today byte-for-byte.
  • Six-phase development plan (skeleton → fs jail → exec → net → config knob → embedder adoption), one PR per phase, TDD.

ADR 0005 D3 (subset default) and D4 (brush-bridle-core deferred) are untouched; #20 stays dormant. Nothing waits on reubeno/brush#1184 anymore.

Test plan

Docs-only PR — no code paths change. just check green (pre-push gate). The ADR's Development-plan section defines the test spine for each implementation phase (envelope parity, fail-closed matrix across ADR 0012 floors, per-axis report assertions).

Out of scope

  • Any engine implementation (phase 1 lands only after this ADR is accepted and phase issues are filed).
  • Auto-fallback routing from the subset engine, per-dispatch engine override, PTY/interactive, Windows (all named in D5).
  • Changes to ADR 0005's decisions — this extends, amends nothing.

Closes nothing; ratification of #194's Option C. Downstream adoption tracked at Gilamonster-Foundation/newt-agent#868.

🤖 Generated with Claude Code

…lopment plan (#194)

A second engine behind the ADR 0005 D2 seam: spawn the OS shell as the
jailed tree root, zero L2 enforcement, every restricted axis held by the
L3 backends (ADR 0011/0013/0014/0015/0016) or honestly refused per the
ADR 0012 floor. Engine selection is an ADR 0017 mechanism knob, never
authority. Status: Proposed — the decision record for issue #194's
Option C, with a six-phase development plan.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@hartsock

hartsock commented Jul 4, 2026

Copy link
Copy Markdown
Member Author

Closing as superseded — the sandboxed-host engine shipped through the renumbered line, all merged to main:

The six-phase plan's intent is captured in ADR 0019's Tracking section + issue #194; the engine landed in one focused PR rather than six phases. Nothing here is lost. Reopen if you'd rather land the phased-plan doc as history — but it would need to be renumbered off 0018 first.

@hartsock hartsock closed this Jul 4, 2026
hartsock added a commit that referenced this pull request Jul 13, 2026
fenced_child_reaches_allowed_via_proxy_denied_refused_direct_kernel_blocked
was the only loopback-exercising test in the net_proxy module that did not
take the module's net_test_lock() serialization guard. It is also the
heaviest net test — it spawns a loopback origin, a proxy, and a real curl
child — so running it concurrently with a sibling proxy test raced on
loopback and flaked CI (observed as "Empty reply from server" on the
allow-leg in PR #195's macOS CI).

Acquire net_test_lock() as the test's first statement, matching every
sibling loopback test (same lock, same RAII scope held through the whole
test), so it can no longer race siblings on loopback.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
hartsock added a commit that referenced this pull request Jul 15, 2026
* test(net-proxy): serialize fenced_child on net_test_lock() (#207)

fenced_child_reaches_allowed_via_proxy_denied_refused_direct_kernel_blocked
was the only loopback-exercising test in the net_proxy module that did not
take the module's net_test_lock() serialization guard. It is also the
heaviest net test — it spawns a loopback origin, a proxy, and a real curl
child — so running it concurrently with a sibling proxy test raced on
loopback and flaked CI (observed as "Empty reply from server" on the
allow-leg in PR #195's macOS CI).

Acquire net_test_lock() as the test's first statement, matching every
sibling loopback test (same lock, same RAII scope held through the whole
test), so it can no longer race siblings on loopback.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(net-proxy): restore net_test_lock() — removed in #216, so the #207 fix could not compile

The one-line fix from #207 called net_test_lock(), but the #216 in-memory
migration (17e7d7b) had removed that helper together with most real-socket
tests. The call sits behind cfg(target_os = "macos", feature =
"macos-seatbelt"), so the Linux dev host never compiled it and macOS CI
failed with E0425 (cannot find function net_test_lock).

Restore the helper — the same poison-immune static-Mutex idiom #155
introduced — and take it in all three remaining real-socket tests
(proxy_env…, fenced_child…, dropping_the_handle…), so the serialization
domain is complete and the helper is used (not dead code under
-D warnings) on every CI platform.

Verified on an M4 mac: CI-parity clippy green; full workspace suite green
under BRIDLE_REQUIRE_SEATBELT=1 with the fenced_child kernel proof
genuinely running (no self-skip); tool-shell suite 5/5 repeat runs green;
--no-default-features workspace tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Shawn Hartsock <hartsock@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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