diff --git a/hive-c0re/src/socket_server/mod.rs b/hive-c0re/src/socket_server/mod.rs index 5d06604b..af5e6254 100644 --- a/hive-c0re/src/socket_server/mod.rs +++ b/hive-c0re/src/socket_server/mod.rs @@ -419,6 +419,17 @@ async fn handle_get_agent_meta( name: Option<&str>, ) -> hive_sh4re::Response { let target = name.unwrap_or(agent); + // `name` is agent-supplied and flows into filesystem reads below + // (`read_agent_status_live`, `read_agent_matrix_identities` → + // `agent_notes_dir(target)`), where a `../` component would traverse at + // the OS level. Validate it before any path is built. The `None` default + // (`target == agent`) is the caller's own authenticated name, already + // valid — but validating unconditionally is simplest and harmless. + if let Some(reason) = crate::dashboard::validate_agent_name(target) { + return hive_sh4re::Response::Err { + message: format!("get_agent_meta: invalid agent name {target:?}: {reason}"), + }; + } let (status_text, status_set_at, running) = crate::container_view::read_agent_status_live(target).await; let (hive_name, swarm_name) = crate::container_view::hive_swarm_names(); @@ -430,6 +441,13 @@ async fn handle_get_agent_meta( status_set_at, hive_name, swarm_name, + // Matrix identities are public handles (`name` / `user_id` + // `@user:server` / `homeserver`) — the access token lives separately + // in the agent's `matrix-token` and is never part of this response. + // Peer visibility is intentional: it lets an agent verify/contact + // another on a public matrix instance. The `validate_agent_name` gate + // above is what closes the real vector here (path traversal via `../` + // in an agent-supplied name). matrix_accounts: read_agent_matrix_identities(target), } }