hive-agent: default-deny ACP permission requests
acp_permits allows tools of the session's MCP servers, the read, edit and search kinds, and fetch with web_tools; every other kind, including execute, other and kinds it doesn't know, is refused. Before, anything but execute (and fetch without web_tools) was allowed, which was only safe while the preset's own config denied the risky built-ins. Refs #4391
This commit is contained in:
parent
a2ab40cc69
commit
868fc789d1
1 changed files with 67 additions and 11 deletions
|
|
@ -9,8 +9,8 @@ use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use hive_runtime::{
|
use hive_runtime::{
|
||||||
AcpRuntime, AgentRuntime, ClaudeRuntime, Config, PercentPolicy, PermissionPolicy, Runtime,
|
AcpRuntime, AgentRuntime, ClaudeRuntime, Config, PercentPolicy, PermissionAsk,
|
||||||
RuntimeSpec, Sink,
|
PermissionPolicy, Runtime, RuntimeSpec, Sink,
|
||||||
};
|
};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
|
|
@ -334,18 +334,33 @@ pub fn make_session(bus: &Bus) -> Result<AgentSession> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Which ACP tool-call kinds an ACP agent may run when it asks. Mirrors the
|
/// The answer to an ACP agent's permission requests, see [`acp_permits`].
|
||||||
/// claude built-ins (`hive_sh4re::permissions`): never a built-in shell
|
|
||||||
/// (`execute`) — shell goes through `mcp__bash__run` — and web access
|
|
||||||
/// (`fetch`) only with the `web_tools` group.
|
|
||||||
fn acp_permission_policy() -> PermissionPolicy {
|
fn acp_permission_policy() -> PermissionPolicy {
|
||||||
let web =
|
let web =
|
||||||
mcp_config::effective_tool_groups().contains(&hive_sh4re::permissions::ToolGroup::WebTools);
|
mcp_config::effective_tool_groups().contains(&hive_sh4re::permissions::ToolGroup::WebTools);
|
||||||
std::sync::Arc::new(move |kind: &str| match kind {
|
std::sync::Arc::new(move |ask: &PermissionAsk<'_>| acp_permits(ask, web))
|
||||||
"execute" => false,
|
}
|
||||||
|
|
||||||
|
/// Whether an ACP agent may run the tool call it asks about. Default-deny,
|
||||||
|
/// allowing what a claude agent has:
|
||||||
|
///
|
||||||
|
/// - tools of the MCP servers the session was handed — which of those an
|
||||||
|
/// agent gets is already decided by its tool groups (`mcp_config`);
|
||||||
|
/// - the file tools, mirroring the claude built-ins
|
||||||
|
/// (`hive_sh4re::permissions`): `read`, `edit`, `search`;
|
||||||
|
/// - `fetch` with the `web_tools` group (`web`).
|
||||||
|
///
|
||||||
|
/// Everything else is refused, including a built-in shell (`execute`) —
|
||||||
|
/// shell goes through `mcp__bash__run` — and any kind this list doesn't name.
|
||||||
|
fn acp_permits(ask: &PermissionAsk<'_>, web: bool) -> bool {
|
||||||
|
if ask.mcp_server.is_some() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
match ask.kind {
|
||||||
|
"read" | "edit" | "search" => true,
|
||||||
"fetch" => web,
|
"fetch" => web,
|
||||||
_ => true,
|
_ => false,
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Drive one turn end-to-end. The durable [`AgentSession`] owns the
|
/// Drive one turn end-to-end. The durable [`AgentSession`] owns the
|
||||||
|
|
@ -795,7 +810,7 @@ fn archive_session(bus: &Bus, session: &AgentSession) {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{nonzero_or, parse_u64};
|
use super::{PermissionAsk, acp_permits, nonzero_or, parse_u64};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn an_absent_or_blank_value_is_not_a_number() {
|
fn an_absent_or_blank_value_is_not_a_number() {
|
||||||
|
|
@ -840,4 +855,45 @@ mod tests {
|
||||||
"rejected: 0 is not a duration"
|
"rejected: 0 is not a duration"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ask(kind: &str) -> PermissionAsk<'_> {
|
||||||
|
PermissionAsk {
|
||||||
|
kind,
|
||||||
|
mcp_server: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn acp_permissions_default_to_deny() {
|
||||||
|
for kind in [
|
||||||
|
"execute",
|
||||||
|
"delete",
|
||||||
|
"move",
|
||||||
|
"switch_mode",
|
||||||
|
"other",
|
||||||
|
"think",
|
||||||
|
"",
|
||||||
|
"new_kind",
|
||||||
|
] {
|
||||||
|
assert!(!acp_permits(&ask(kind), true), "{kind:?} was allowed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn acp_file_tools_and_mcp_servers_are_allowed() {
|
||||||
|
for kind in ["read", "edit", "search"] {
|
||||||
|
assert!(acp_permits(&ask(kind), false), "{kind:?} was refused");
|
||||||
|
}
|
||||||
|
let mcp = PermissionAsk {
|
||||||
|
kind: "other",
|
||||||
|
mcp_server: Some("hyperhive"),
|
||||||
|
};
|
||||||
|
assert!(acp_permits(&mcp, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn acp_fetch_follows_the_web_tools_group() {
|
||||||
|
assert!(acp_permits(&ask("fetch"), true));
|
||||||
|
assert!(!acp_permits(&ask("fetch"), false));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue