From c1f7bb798e4ac6243b167ac67f46114a65d05315 Mon Sep 17 00:00:00 2001 From: damocles Date: Fri, 6 Nov 2026 23:28:05 +0100 Subject: [PATCH] hive-agent-mcp: the send allow-list is gone, agents talk to peers over matrix --- docs/4-implementation/turn-loop/mcp.md | 4 +- hive-agent-mcp/README.md | 2 - hive-agent-mcp/src/main.rs | 1 - hive-agent-mcp/src/mcp/mod.rs | 5 --- hive-agent-mcp/src/send_allow.rs | 61 -------------------------- nix/agent-modules/default.nix | 12 +++++ nix/agent-modules/mcp.nix | 24 ---------- nix/docs/default.nix | 2 +- 8 files changed, 14 insertions(+), 97 deletions(-) delete mode 100644 hive-agent-mcp/src/send_allow.rs diff --git a/docs/4-implementation/turn-loop/mcp.md b/docs/4-implementation/turn-loop/mcp.md index 543bd86a..02c58fe8 100644 --- a/docs/4-implementation/turn-loop/mcp.md +++ b/docs/4-implementation/turn-loop/mcp.md @@ -44,9 +44,7 @@ runs on the change. `get_agent_meta`'s `matrix_accounts`); `send` is for peers without a matrix account and agents without matrix tools. Optional `in_reply_to: i64` links the message to a prior id for thread - rendering. Per-agent `services.hyperhive.agent.allowedRecipients` - (default: empty = unrestricted) limits which names `send` accepts — - useful for sandboxing. The broker refuses `to: "operator"`. The error + rendering. The broker refuses `to: "operator"`. The error points an agent that has the matrix MCP tools at matrix ([matrix](../agent-tools/matrix.md)), and tells one without them that the operator reads its `set_status` line, its turn output on the dashboard, diff --git a/hive-agent-mcp/README.md b/hive-agent-mcp/README.md index 381fd590..e84576ea 100644 --- a/hive-agent-mcp/README.md +++ b/hive-agent-mcp/README.md @@ -23,8 +23,6 @@ or their own daemon instead of living here. (`/run/hive/mcp.sock`) or, for loose-ends v2 (todos/reminders), the in-agent socket the `hive-agent` harness serves. - **`client.rs`** — socket client to the hyperhive broker. -- **`send_allow.rs`** — enforces the per-agent - `services.hyperhive.agent.allowedRecipients` allow-list on `send`. - **`paths.rs`** — socket + state path resolution shared with the harness's own `paths.rs` conventions. diff --git a/hive-agent-mcp/src/main.rs b/hive-agent-mcp/src/main.rs index 4b2fd049..cedc8589 100644 --- a/hive-agent-mcp/src/main.rs +++ b/hive-agent-mcp/src/main.rs @@ -18,7 +18,6 @@ use clap::Parser; mod mcp; mod paths; -mod send_allow; /// Per-agent MCP socket, bind-mounted from the host into every container. const DEFAULT_SOCKET: &str = "/run/hive/mcp.sock"; diff --git a/hive-agent-mcp/src/mcp/mod.rs b/hive-agent-mcp/src/mcp/mod.rs index a4658de5..3c3fd26c 100644 --- a/hive-agent-mcp/src/mcp/mod.rs +++ b/hive-agent-mcp/src/mcp/mod.rs @@ -150,11 +150,6 @@ impl AgentServer { async fn send(&self, Parameters(args): Parameters) -> String { let log = format!("{args:?}"); let to = args.to.clone(); - // Check per-agent allow-list (services.hyperhive.agent.allowedRecipients). When no - // policy file is present the check is a no-op. - if let Err(refusal) = crate::send_allow::check_send_allowed(&to) { - return run_tool_envelope("send", log, async move { refusal }).await; - } run_tool_envelope("send", log, async move { let (resp, retries) = self .dispatch(hive_core_agent_sock::Request::Send { diff --git a/hive-agent-mcp/src/send_allow.rs b/hive-agent-mcp/src/send_allow.rs deleted file mode 100644 index 5d3fbb8c..00000000 --- a/hive-agent-mcp/src/send_allow.rs +++ /dev/null @@ -1,61 +0,0 @@ -//! Per-agent `send` allow-list enforcement. Driven by the operator config at -//! `/etc/hyperhive/send-allow.json` (written by the NixOS harness module); the -//! MCP server calls [`check_send_allowed`] before forwarding a `send` tool call -//! so a blocked recipient surfaces as a claude-readable tool result rather than -//! a silent drop. - -/// Where the NixOS module writes the per-agent send allow-list (see -/// `nix/agent-modules/mcp.nix`). Empty list = unrestricted (the -/// default). Non-empty list constrains `mcp__hyperhive__send`'s `to` -/// field. -const SEND_ALLOW_PATH: &str = "/etc/hyperhive/send-allow.json"; - -/// Enforce the per-agent send allow-list. Returns `Ok` when the -/// recipient is permitted (no list configured, or `to` is in the list); -/// returns `Err(refusal)` with a claude-readable string when blocked — -/// the harness surfaces the refusal as the tool result so claude knows -/// the message didn't land and can react. `operator` is refused whatever -/// the list says, with the same matrix-pointing text the broker returns. -pub fn check_send_allowed(to: &str) -> Result<(), String> { - if to == hive_sh4re::manager::OPERATOR_RECIPIENT { - return Err(hive_sh4re::manager::OPERATOR_SEND_REFUSAL.to_owned()); - } - let Ok(raw) = std::fs::read_to_string(SEND_ALLOW_PATH) else { - return Ok(()); // file missing → no policy configured → unrestricted - }; - let allow: Vec = match serde_json::from_str(&raw) { - Ok(v) => v, - Err(e) => { - tracing::warn!( - path = SEND_ALLOW_PATH, - error = ?e, - "send allow-list parse failed; falling back to unrestricted", - ); - return Ok(()); - } - }; - if allow.is_empty() { - return Ok(()); // empty list = unrestricted (back-compat) - } - if allow.iter().any(|n| n == to) { - return Ok(()); - } - Err(format!( - "send refused: recipient '{to}' not in services.hyperhive.agent.allowedRecipients \ - (configured in agent.nix). Allowed: {allow:?}. To reach the operator, use \ - the matrix MCP tools if you have them." - )) -} - -#[cfg(test)] -mod tests { - use super::check_send_allowed; - - #[test] - fn operator_is_refused_naming_matrix_and_the_no_matrix_fallback() { - let refusal = check_send_allowed(hive_sh4re::manager::OPERATOR_RECIPIENT) - .expect_err("operator must be refused"); - assert!(refusal.contains("mcp__matrix__"), "{refusal}"); - assert!(refusal.contains("set_status"), "{refusal}"); - } -} diff --git a/nix/agent-modules/default.nix b/nix/agent-modules/default.nix index 2ee8ce80..e4a757ec 100644 --- a/nix/agent-modules/default.nix +++ b/nix/agent-modules/default.nix @@ -34,6 +34,12 @@ let sets it. Pin it host-side with services.hyperhive.deploy.hive-controller.claudeCodePackage. ''; + sendAllowRemoved = '' + The `send` allow-list is gone: it limited only the hive-local `send` tool, + which is not the channel agents use to reach peers, and it was never a + boundary. Agents talk to peers over matrix. Remove the setting. + ''; + queueRemoved = '' The harness reads the queue's address at runtime from HIVE_AGENT_NATS_URL, in /etc/hyperhive-hive-facts, and authenticates with the @@ -115,6 +121,12 @@ in "agent" "claudeCodePath" ] claudeCodePathRemoved) + (lib.mkRemovedOptionModule [ + "services" + "hyperhive" + "agent" + "allowedRecipients" + ] sendAllowRemoved) (lib.mkRemovedOptionModule [ "services" "hyperhive" "agent" "queue" "natsUrl" ] queueRemoved) (lib.mkRemovedOptionModule [ "services" "hyperhive" "agent" "queue" "tokenEndpoint" ] queueRemoved) ]; diff --git a/nix/agent-modules/mcp.nix b/nix/agent-modules/mcp.nix index b06286e7..434bec48 100644 --- a/nix/agent-modules/mcp.nix +++ b/nix/agent-modules/mcp.nix @@ -22,27 +22,6 @@ let subagentReadsStore = config.services.hyperhive.agent.bao.addr != null; in { - options.services.hyperhive.agent.allowedRecipients = lib.mkOption { - type = lib.types.listOf lib.types.str; - default = [ ]; - example = [ - "alice" - "bob" - ]; - description = '' - Names this agent is allowed to `send` to via - `mcp__hyperhive__send`. Empty list (the default) means - unrestricted — the agent can message any peer. A non-empty list - allows only the listed names; anything else returns an error - string to claude without touching the broker. - `operator` is refused either way: agents reach the operator - over matrix. - - Useful for sandboxing untrusted sub-agents — set - `[ "alice" ]` to scope one to chatter with alice. - ''; - }; - options.services.hyperhive.agent.extraMcpServers = lib.mkOption { type = lib.types.attrsOf ( lib.types.submodule { @@ -304,9 +283,6 @@ in environment.etc."hyperhive/extra-mcp.json".text = builtins.toJSON config.services.hyperhive.agent.extraMcpServers; - environment.etc."hyperhive/send-allow.json".text = - builtins.toJSON config.services.hyperhive.agent.allowedRecipients; - # Bash task runner daemon — long-running process that owns subprocess # monitoring + completion wake signals, and serves the MCP tools # (`run`/`status`/`kill`) directly over streamable-http on diff --git a/nix/docs/default.nix b/nix/docs/default.nix index ae002cca..9320ffba 100644 --- a/nix/docs/default.nix +++ b/nix/docs/default.nix @@ -237,7 +237,7 @@ let grafana, the swarm controller, authelia, …). - [per-agent options](agent.md) — options declared in `nix/agent-modules/`, visible from every `agent.nix` - (`services.hyperhive.agent.model`, `services.hyperhive.agent.allowedRecipients`, + (`services.hyperhive.agent.model`, `services.hyperhive.agent.extraMcpServers`, `services.hyperhive.agent.frontend.*`, `services.hyperhive.agent.forge.*`, `services.hyperhive.agent.matrix.*`, `services.hyperhive.agent.gui.*`).