Skip to main content

agent_bridle_tool_shell/
lib.rs

1//! `agent-bridle-tool-shell` — capability-confined safe-subset, Brush, and host-shell engines.
2//!
3//! Per **ADR 0005** the object-capability *boundary* is L3 (kernel) and this
4//! crate supplies complementary L2 engines. The lean [`ShellTool`] is an
5//! **exec funnel**: it parses each request itself ([`crate::parse`]), accepts
6//! argv form (`program` + `args`) or a restricted free-form `cmd`, checks the
7//! `exec`/`fs` leash, spawns directly, and refuses dynamic constructs by
8//! design (command substitution `$(...)`, arithmetic expansion `$((...))`,
9//! backticks, and subshells). [`BrushShellTool`] instead carries full Brush
10//! grammar in a dedicated worker; its interceptor gates the worker's own
11//! external spawns and opens. When effective caveats engage an available
12//! Landlock, Seatbelt, or AppContainer backend, the worker/process tree
13//! inherits that L3 boundary; otherwise the run honestly reports
14//! [`agent_bridle_core::SandboxKind::None`] (I9). Coverage is per-axis and
15//! scope-shaped: inspect the result's enforcement report rather than inferring
16//! every guarantee from the coarse sandbox kind.
17//!
18//! The engine (agent-bridle#34 Track A + #45): a sequence of pipelines joined by
19//! `&&`/`||`/`;` (short-circuit semantics), each pipeline simple commands with
20//! quoted arguments, **redirections** (`> out`, `>> out`, `< in`, `2> err`,
21//! `2>&1`), **filename globbing** (`*`/`?`/`[…]`) and **allowlisted `$VAR`
22//! expansion** — every filesystem/env touch bridle performs (redirect opens,
23//! glob directory listings, variable allowlist) is leash-/policy-checked before
24//! any spawn. Those dynamic constructs stay refused by the safe-subset engine.
25//! The process spawning is behind a `Spawner` seam (mocked in unit tests; real
26//! path in `tests/real_spawn.rs`). Brush is the carried full-grammar alternative
27//! behind the same construction-time registry seam.
28
29#![forbid(unsafe_code)]
30#![warn(missing_docs)]
31
32#[cfg(feature = "brush")]
33mod brush_shell;
34#[cfg(feature = "brush")]
35mod brush_worker;
36#[cfg(feature = "brush")]
37mod caveat_interceptor;
38#[cfg(feature = "brush")]
39mod coreutils_dispatch;
40#[cfg(all(feature = "brush", any(target_os = "linux", target_os = "macos")))]
41mod private_control;
42#[cfg(all(feature = "brush", not(any(target_os = "linux", target_os = "macos"))))]
43mod private_control {
44    use serde::de::DeserializeOwned;
45
46    pub(crate) fn receive_worker_request<P: DeserializeOwned>(
47    ) -> Result<agent_bridle_core::TrustedWorkerRequest<P>, String> {
48        Err("authenticated private worker control is unavailable on this platform".to_string())
49    }
50
51    #[cfg(feature = "carried-coreutils")]
52    pub(crate) fn authenticate_carried_dispatch(
53        _name: &std::ffi::OsStr,
54        _args: &[std::ffi::OsString],
55    ) -> Result<(), String> {
56        Err("authenticated carried dispatch is unavailable on this platform".to_string())
57    }
58}
59#[cfg(feature = "host-shell")]
60mod host_shell;
61#[cfg(feature = "brush")]
62mod shell_inspect;
63// #257: the loopback egress proxy moved to agent-bridle-core (shared with
64// `ConfinedCommand::spawn_tokio` and external no-subprocess callers). This
65// alias keeps every `crate::net_proxy::…` path — and the audit re-exports
66// below — resolving unchanged, now to the single core implementation.
67#[cfg(feature = "shell")]
68pub(crate) use agent_bridle_core::net_proxy;
69mod output_observer;
70#[cfg(feature = "shell")]
71mod parse;
72#[cfg(feature = "shell")]
73mod shell_tool;
74
75pub use output_observer::{ShellInvocationId, ShellOutputObserver, ShellOutputStream};
76#[cfg(feature = "shell")]
77pub use shell_tool::ShellTool;
78
79/// The sandboxed-host engine (ADR 0019 / #194): full-shell semantics with the
80/// guarantee entirely on L3. Opt-in via the `host-shell` feature; a
81/// construction-time alternative to [`ShellTool`] behind the ADR 0005 D2 seam.
82#[cfg(feature = "host-shell")]
83pub use host_shell::HostShellTool;
84
85/// The carried **brush** engine (agent-bridle#20 / Track 2): a bash-in-Rust
86/// shell run in a dedicated sandboxed worker. Its `CommandInterceptor` provides
87/// the worker-local L2 leash; when an effective native backend engages, the
88/// worker and descendants inherit that L3 boundary. Opt-in via the `brush`
89/// feature; a construction-time alternative to [`ShellTool`] behind the ADR
90/// 0005 D2 seam, using the temporary `brush-ocap-*` fork
91/// (reubeno/brush#1184).
92#[cfg(feature = "brush")]
93pub use brush_shell::BrushShellTool;
94
95/// Whether this target provides the kernel-authenticated private transport
96/// required by [`BrushShellTool`] and carried-coreutils re-exec.
97///
98/// A host must use this probe before advertising or selecting the Brush engine.
99/// Unsupported targets fail closed at invocation; they must select the
100/// safe-subset [`ShellTool`] instead of treating full access as authentication.
101#[cfg(feature = "brush")]
102#[must_use]
103pub const fn brush_private_control_supported() -> bool {
104    cfg!(any(target_os = "linux", target_os = "macos"))
105}
106#[cfg(feature = "brush")]
107pub use shell_inspect::{
108    inspect_shell, DescendantExec, InspectedCommand, InspectedConstruct, InspectedRedirect,
109    RedirectOperation, ShellConstructKind, ShellInspection, ShellInspectionError,
110};
111
112/// Private Brush-worker dispatch. An embedder's binary calls
113/// [`maybe_dispatch`] at the top of `main` so the sandboxed worker re-exec
114/// resolves before normal application startup.
115#[cfg(feature = "brush")]
116pub use coreutils_dispatch::maybe_dispatch;
117
118/// Carried-coreutils registration (agent-bridle#20 / issue #206). With
119/// `carried-coreutils`, the Brush engine's non-conflicting shims re-exec
120/// `<self> --invoke-bundled <name>` and resolve against the dispatch-capable
121/// host binary. These functions are used by the engine internally.
122#[cfg(feature = "carried-coreutils")]
123pub use coreutils_dispatch::{install_default_providers, register_shims};
124
125/// Network egress audit surface (#124, ADR 0016): the loopback proxy records
126/// every proxy-visible connection as a [`NetAuditEvent`] through an [`AuditSink`]
127/// (default off; enable via the `BRIDLE_NET_AUDIT` setting). The `bridle-netmon`
128/// binary renders the JSON-lines stream as a live monitor.
129#[cfg(feature = "shell")]
130pub use net_proxy::{AuditSink, JsonlSink, NetAuditEvent, NetDecision, NetKind, NullSink};