Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,634 changes: 1,601 additions & 33 deletions Cargo.lock

Large diffs are not rendered by default.

39 changes: 35 additions & 4 deletions agent-bridle-tool-shell/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ authors.workspace = true
name = "bridle-netmon"
required-features = ["shell"]

# A dispatch-capable host binary for the carried-coreutils integration test
# (issue #206): its `main` calls `maybe_dispatch()` first, so the brush engine's
# coreutils shims (which re-exec `<self> --invoke-bundled <name>`) resolve
# in-process against THIS binary — proving carried `ls`/`cat` work with no host
# tools. Only built with `carried-coreutils`.
[[bin]]
name = "dispatch_host"
required-features = ["carried-coreutils"]

[features]
# `shell` is on by default so agent-bridle's registry() includes the tool.
# It pulls tokio (spawn_blocking + timeout) for confined execution.
Expand All @@ -29,6 +38,18 @@ shell = ["dep:tokio"]
# Opt-in; the embedder registers it instead of the safe-subset ShellTool. Pulls
# tokio (spawn_blocking) like `shell`; independent of the safe-subset modules.
host-shell = ["dep:tokio"]
# The carried **brush** engine (agent-bridle#20 / Track 2): a bash-in-Rust shell
# run in-process, confined by the `CommandInterceptor` L2 leash — the only engine
# that also confines a *restricted* exec/net grant, and cross-platform. Uses the
# temporary `brush-ocap-*` fork, published to crates.io (reubeno/brush#1184 not
# yet upstream). Off by default; the crate stays publish-clean with `brush` off.
brush = ["dep:tokio", "dep:brush-ocap-core", "dep:brush-ocap-builtins"]
# Carried coreutils (agent-bridle#20 / issue #206): the brush engine registers
# bundled uu_* coreutils shims that re-exec `<self> --invoke-bundled <name>`, so
# an embedded shell runs `ls`/`cat`/… with NO host tools — IF the host binary is
# dispatch-capable (calls `maybe_dispatch()` at the top of `main`). Off by
# default; carries the fork's published coreutils crate.
carried-coreutils = ["brush", "dep:brush-ocap-coreutils-builtins"]
# Kernel-enforced L3 backstop (Linux). The engine applies the Landlock fs_write
# ruleset before spawning when fs_write is restricted (agent-bridle#35); off this
# build/OS it is the honest advisory None (ADR 0005 / ADR 0006). Exec/net axes
Expand All @@ -53,10 +74,20 @@ serde_json = { workspace = true }
async-trait = { workspace = true }
tokio = { workspace = true, optional = true }

# brush deps — intentionally absent. `brush-bridle-core` (a renamed brush fork
# published to crates.io) is the DEFERRED, REVERSIBLE full-bash alternative
# engine behind the same registry seam (ADR 0005 D4); it would be added under
# its own optional feature when adopted, not restored here. See agent-bridle#20.
# The `brush-ocap-*` fork on crates.io (github.com/hartsock/brush @ brush-ocap):
# upstream brush + the CommandInterceptor hook (reubeno/brush#1184), renamed +
# published so this crate is crates.io-publishable with `brush` on. Temporary —
# retire once #1184 merges upstream and a hooked `brush-*` release ships
# (agent-bridle#20).
brush-ocap-core = { version = "0.5", optional = true }
brush-ocap-builtins = { version = "0.2", optional = true }
# A minimal carried coreutils set (ls/cat/echo) — enough for the embedded
# dispatch path (issue #206) without the ~100-crate `coreutils.all` build.
brush-ocap-coreutils-builtins = { version = "0.1", features = [
"coreutils.ls",
"coreutils.cat",
"coreutils.echo",
], optional = true }

[dev-dependencies]
tokio = { workspace = true }
Expand Down
51 changes: 51 additions & 0 deletions agent-bridle-tool-shell/src/bin/dispatch_host.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//! Dispatch-capable host binary for the carried-coreutils integration test
//! (issue #206). Its `main` calls [`maybe_dispatch`] first, so the brush engine's
//! carried coreutils shims — which re-exec `<self> --invoke-bundled <name>` —
//! resolve **in-process against THIS binary**. Run it with the environment
//! scrubbed (`env_clear`) to prove carried `ls`/`cat` work with no host tools.
//!
//! Usage: `dispatch_host <cmd> [cwd]` — runs `<cmd>` through the confined brush
//! engine under full-access, prints captured stdout/stderr, exits with the
//! command's code.

use agent_bridle_core::{Caveats, Gate, Tool};
use agent_bridle_tool_shell::{maybe_dispatch, BrushShellTool};

fn main() {
// MUST be first: if invoked as `<self> --invoke-bundled <name> …`, run the
// carried coreutil in-process (a fresh post-exec process — safe) and exit.
if let Some(code) = maybe_dispatch() {
std::process::exit(code);
}

let mut args = std::env::args().skip(1);
let cmd = args.next().expect("usage: dispatch_host <cmd> [cwd]");
let cwd = args.next();

let tool = BrushShellTool::new();
let ctx = Gate::new(0)
.authorize(&tool, &Caveats::top())
.expect("authorize");

let mut json = serde_json::json!({ "cmd": cmd });
if let Some(cwd) = cwd {
json["cwd"] = serde_json::Value::String(cwd);
}

let rt = tokio::runtime::Runtime::new().expect("tokio runtime");
let out = rt
.block_on(async { tool.invoke(json, &ctx).await })
.expect("invoke");

if let Some(s) = out.get("stdout").and_then(|v| v.as_str()) {
print!("{s}");
}
if let Some(s) = out.get("stderr").and_then(|v| v.as_str()) {
eprint!("{s}");
}
let code = out
.get("exit_code")
.and_then(serde_json::Value::as_i64)
.unwrap_or(1);
std::process::exit(code as i32);
}
Loading
Loading