Skip to content

feat(shell): carried brush engine (BrushShellTool) [DRAFT — needs brush-ocap-* published]#204

Merged
hartsock merged 3 commits into
mainfrom
feat/brush-ocap-engine
Jul 6, 2026
Merged

feat(shell): carried brush engine (BrushShellTool) [DRAFT — needs brush-ocap-* published]#204
hartsock merged 3 commits into
mainfrom
feat/brush-ocap-engine

Conversation

@hartsock

@hartsock hartsock commented Jul 6, 2026

Copy link
Copy Markdown
Member

Summary

Restores 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).

Why it's distinct: 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 grant in-process, on any platform, recording each denial as structured denials. 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 are unchanged.
  • brush_shell.rs is a fresh, minimal engine (modeled on HostShellTool): OS pipes + Shell::builder_with_extensions + interceptor + curated builtins, output captured.
  • PARITY: seeds the full ambient PATH (default_exec_path()) when exec is unrestricted; a minimal standard PATH when restricted (externals still resolve to reach the deny gate).
  • brush feature (off by default) on tool-shell + facade; facade re-exports BrushShellTool. 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.
  • Full tool-shell suite green with brush,host-shell,linux-landlock (111 tests); cargo clippy -D warnings clean; default build (brush off) clean; core publish-check green.

⚠️ Draft — do not merge yet

While brush is on, this carries a git dep on hartsock/brush @ ee58e24, so agent-bridle-tool-shell / the facade cannot publish to crates.io. CI is green because the publishability gate only checks agent-bridle-core (which stays clean), but merging would break just publish-crates.

To land: publish brush-ocap-{core,builtins} to crates.io → swap the two git deps in agent-bridle-tool-shell/Cargo.toml to crates.io version deps → un-draft → merge (agent-bridle#20). Then newt's brush engine value (currently falls back to host) can be wired to it.

🤖 Generated with Claude Code

… `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
hartsock and others added 2 commits July 6, 2026 13:07
…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 hartsock marked this pull request as ready for review July 6, 2026 17:54
@hartsock hartsock merged commit c054587 into main Jul 6, 2026
10 checks passed
@hartsock hartsock deleted the feat/brush-ocap-engine branch July 6, 2026 17:58
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>
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