harness: gate the bash mcp server behind the execution tool group
This commit is contained in:
parent
6c3a83ffa1
commit
3795ffc1c6
1 changed files with 67 additions and 1 deletions
|
|
@ -1840,6 +1840,60 @@ fn effective_tool_groups() -> Vec<hive_sh4re::ToolGroup> {
|
|||
groups
|
||||
}
|
||||
|
||||
/// Tool group an extra (out-of-process) MCP server is gated behind, if any.
|
||||
///
|
||||
/// Most `hyperhive.extraMcpServers` entries are ungated — available whenever
|
||||
/// the operator declares them. The `bash` server is the exception: raw shell
|
||||
/// execution is a privilege, so it is only exposed when the agent holds the
|
||||
/// `Execution` tool group. Unlike the in-process hyperhive tools (gated at
|
||||
/// dispatch) and the capability tools (re-checked server-side by hive-c0re),
|
||||
/// an out-of-process server has **no** later enforcement point — once it is
|
||||
/// in the claude MCP config the agent can call it. So this gate, applied at
|
||||
/// config-render time, is the security boundary for those servers.
|
||||
fn extra_server_required_group(server: &str) -> Option<hive_sh4re::ToolGroup> {
|
||||
match server {
|
||||
"bash" => Some(hive_sh4re::ToolGroup::Execution),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether an extra MCP server should be exposed to claude given the active
|
||||
/// tool `groups`. A gated server (see [`extra_server_required_group`]) is
|
||||
/// suppressed when the agent lacks its required group.
|
||||
fn extra_server_enabled(server: &str, groups: &[hive_sh4re::ToolGroup]) -> bool {
|
||||
extra_server_required_group(server).is_none_or(|required| groups.contains(&required))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod extra_server_gate_tests {
|
||||
use super::{extra_server_enabled, extra_server_required_group};
|
||||
use hive_sh4re::ToolGroup;
|
||||
|
||||
#[test]
|
||||
fn bash_is_gated_behind_execution() {
|
||||
assert_eq!(
|
||||
extra_server_required_group("bash"),
|
||||
Some(ToolGroup::Execution)
|
||||
);
|
||||
// Suppressed without Execution, even if other groups are present.
|
||||
assert!(!extra_server_enabled(
|
||||
"bash",
|
||||
&[ToolGroup::Messaging, ToolGroup::Inbox]
|
||||
));
|
||||
// Available once Execution is granted.
|
||||
assert!(extra_server_enabled("bash", &[ToolGroup::Execution]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn other_servers_are_ungated() {
|
||||
assert_eq!(extra_server_required_group("matrix"), None);
|
||||
assert_eq!(extra_server_required_group("scraper"), None);
|
||||
// An ungated server is available regardless of (even empty) groups.
|
||||
assert!(extra_server_enabled("matrix", &[]));
|
||||
assert!(extra_server_enabled("scraper", &[ToolGroup::Messaging]));
|
||||
}
|
||||
}
|
||||
|
||||
/// MCP tools claude is allowed to call without prompting, derived from
|
||||
/// the supplied tool groups. Adding a new `#[tool]` fn to a server impl
|
||||
/// requires updating the matching `ToolGroup::tools()` slice in hive-sh4re
|
||||
|
|
@ -1864,7 +1918,7 @@ pub fn allowed_mcp_tools(groups: &[hive_sh4re::ToolGroup]) -> Vec<String> {
|
|||
// them without per-tool operator approval. `["*"]` (the default)
|
||||
// expands to `mcp__<server>__*` — every tool from that server.
|
||||
for (server, spec) in load_extra_mcp() {
|
||||
if server == SERVER_NAME {
|
||||
if server == SERVER_NAME || !extra_server_enabled(&server, groups) {
|
||||
continue;
|
||||
}
|
||||
for pat in spec.allowed_tools {
|
||||
|
|
@ -2040,6 +2094,11 @@ pub fn render_claude_config(agent_binary: &str, socket: &std::path::Path) -> Str
|
|||
// agent's durable state dir without the agent author hard-coding it.
|
||||
// User-supplied env takes precedence — we only fill in the missing key.
|
||||
let state_dir = crate::paths::state_dir();
|
||||
// Gate tool-group-restricted extra servers (e.g. `bash` → `Execution`).
|
||||
// This is the security boundary for them: an out-of-process server the
|
||||
// agent isn't entitled to must not even appear in the MCP config, or the
|
||||
// agent could call it directly (there is no later enforcement point).
|
||||
let groups = effective_tool_groups();
|
||||
for (name, mut spec) in load_extra_mcp() {
|
||||
if name == SERVER_NAME {
|
||||
tracing::warn!(
|
||||
|
|
@ -2047,6 +2106,13 @@ pub fn render_claude_config(agent_binary: &str, socket: &std::path::Path) -> Str
|
|||
);
|
||||
continue;
|
||||
}
|
||||
if !extra_server_enabled(&name, &groups) {
|
||||
tracing::info!(
|
||||
server = %name,
|
||||
"extra MCP server suppressed: agent lacks the required tool group"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
spec.env
|
||||
.entry("HYPERHIVE_STATE_DIR".to_owned())
|
||||
.or_insert_with(|| state_dir.display().to_string());
|
||||
|
|
|
|||
Loading…
Reference in a new issue