feat(shell): carried brush engine (BrushShellTool) [DRAFT — needs brush-ocap-* published]#204
Merged
Conversation
… `brush` feature Bring back the in-process, CommandInterceptor-confined bash engine as a third engine behind the ADR 0005 D2 seam (peer of ShellTool / HostShellTool), using the temporary `brush-ocap-*` fork (upstream brush + reubeno/brush#1184). Unlike HostShellTool — which refuses a restricted exec/net grant because it can't bound a real /bin/sh's children — the brush engine's interceptor fires on every resolved program name (before_exec) and opened path (before_open) *inside* brush, so it CONFINES a restricted exec/fs grant in-process, on any platform, and records each denial as structured `denials` on the envelope. `exec` is stripped from the curated builtin set (the one builtin that spawns outside the funnel). - `caveat_interceptor.rs` recovered from history (5adfd91) — its core deps (ToolContext::check_exec/check_path_*, Denial/DenialKind) are unchanged. - `brush_shell.rs` is a fresh, minimal engine (modeled on HostShellTool): OS pipes + `Shell::builder_with_extensions` + the interceptor + curated builtins, run on a per-invocation current-thread runtime, output captured. - PARITY: seeds the FULL ambient PATH (`default_exec_path()`) when exec is unrestricted (agent's own tools resolve like a host shell); a minimal standard PATH when exec is restricted (externals still resolve to reach the before_exec gate that denies the out-of-scope ones). - Feature-gated `brush` (off by default) on tool-shell + the facade; the facade re-exports `BrushShellTool`. Default build + core stay publish-clean. Tests (tests/brush_real.rs, real spawn): `$(...)` runs in-process (safe-subset refuses it); a restricted-exec out-of-scope command is DENIED and never runs; in-scope runs. Full tool-shell suite green with brush+host-shell+landlock; clippy -D warnings clean. NOT MERGEABLE YET: while `brush` is on, this carries a GIT dep on the fork, so tool-shell/facade can't publish to crates.io. To land: publish `brush-ocap-*`, swap the git deps to crates.io version deps, then merge (agent-bridle#20). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HRv5vteHCw4jUayHGL5esx
…rary (#206) Make brush's bundled uu_* coreutils (ls/cat/…) usable from the EMBEDDED brush engine with no host tools — the "just a filesystem" / distroless / Windows story (Track 2 Gate 2). Previously the dispatcher lived only in the brush *binary*, so an embedder's carried coreutils never ran. - `coreutils_dispatch.rs` lifts the dispatcher + shim registration into a library (cribbed from the fork's `brush-shell/src/bundled.rs`): `maybe_dispatch()`, `install_default_providers()`, `register_shims()`, `shim_execute()`. An embedder makes its binary dispatch-capable with one line in `main`: `if let Some(c) = maybe_dispatch() { std::process::exit(c); }`. - We keep the RE-EXEC (fork+exec) model deliberately: "fork + uumain in-process" is unsafe in a multithreaded process (fork-without-exec deadlocks on allocator locks; uumain isn't async-signal-safe). execve resets the address space, so re-exec is safe AND portable (Windows too). - `BrushShellTool` registers the coreutils shims when `carried-coreutils` is on; the shim's re-exec still funnels through the `before_exec` interceptor (leash holds). Feature `carried-coreutils` (off by default) carries the fork's coreutils crate (git dep) + a minimal ls/cat/echo uutils set. Runnable proof: `dispatch_host` bin (dispatch-capable) + `tests/carried_coreutils.rs` run carried `ls`/`cat` with the environment SCRUBBED (`env_clear` — no PATH, no host tools) and assert the output. Full suite green with carried-coreutils,host-shell,linux-landlock (114 tests); clippy -D warnings clean; default build + core publish-check unaffected. To upstream: the dispatch-as-a-library move is a natural follow-up to the CommandInterceptor hook (reubeno/brush#1184). Refs #206, #204, agent-bridle#20. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HRv5vteHCw4jUayHGL5esx
… versions Now that brush-ocap-core 0.5.0, brush-ocap-builtins 0.2.0, and brush-ocap-coreutils-builtins 0.1.0 are published to crates.io, the `brush` / `carried-coreutils` features use version deps instead of the `hartsock/brush` git rev. The crate is git-dep-free and publishable with the features on. Facade gains a `carried-coreutils` feature forwarding to tool-shell. Behavior is identical (crates.io == fork rev ee58e24): brush_real + carried_coreutils green, builds resolve `registry+…crates.io` for all three brush-ocap-* crates. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HRv5vteHCw4jUayHGL5esx
hartsock
added a commit
that referenced
this pull request
Jul 6, 2026
Cut the 0.7.0-rc.1 line carrying the OCAP shell-engine work: the sandbox-host engine + full-access PATH seed (#203), the carried brush engine + carried coreutils (#204/#206, via the published brush-ocap-* crates), and the honest per-axis L3 disclosure. The stale crates.io `agent-bridle-core 0.1.0` predated the macos-seatbelt / windows-appcontainer / host-shell features, so republishing the workspace at 0.7.0-rc.1 is what lets `agent-bridle-tool-shell` publish with those features (the pre-existing release blocker F surfaced during the brush dep-swap). Claude-Session: https://claude.ai/code/session_01HRv5vteHCw4jUayHGL5esx Co-authored-by: Shawn Hartsock <hartsock@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Restores the in-process,
CommandInterceptor-confined bash engine as a third engine behind the ADR 0005 D2 seam (peer ofShellTool/HostShellTool), using the temporarybrush-ocap-*fork (upstream brush + reubeno/brush#1184).Why it's distinct: unlike
HostShellTool— which refuses a restrictedexec/netgrant because it can't bound a real/bin/sh's children — the brush engine's interceptor fires on every resolved program name (before_exec) and opened path (before_open) inside brush, so it confines a restricted grant in-process, on any platform, recording each denial as structureddenials.execis stripped from the curated builtin set (the one builtin that spawns outside the funnel).caveat_interceptor.rsrecovered from history (5adfd91) — its core deps are unchanged.brush_shell.rsis a fresh, minimal engine (modeled onHostShellTool): OS pipes +Shell::builder_with_extensions+ interceptor + curated builtins, output captured.PATH(default_exec_path()) whenexecis unrestricted; a minimal standardPATHwhen restricted (externals still resolve to reach the deny gate).brushfeature (off by default) on tool-shell + facade; facade re-exportsBrushShellTool. Default build + core stay publish-clean.Test plan
tests/brush_real.rs(real spawn):$(...)runs in-process (safe-subset refuses it); a restricted-exec out-of-scope command is denied and never runs; in-scope runs.brush,host-shell,linux-landlock(111 tests);cargo clippy -D warningsclean; default build (brush off) clean; corepublish-checkgreen.While
brushis on, this carries a git dep onhartsock/brush @ ee58e24, soagent-bridle-tool-shell/ the facade cannot publish to crates.io. CI is green because the publishability gate only checksagent-bridle-core(which stays clean), but merging would breakjust publish-crates.To land: publish
brush-ocap-{core,builtins}to crates.io → swap the two git deps inagent-bridle-tool-shell/Cargo.tomlto crates.io version deps → un-draft → merge (agent-bridle#20). Then newt'sbrushengine value (currently falls back tohost) can be wired to it.🤖 Generated with Claude Code