From 470af7051c9d84a11528c80f604243e87fc7cb2e Mon Sep 17 00:00:00 2001 From: argenis de la rosa Date: Sat, 28 Feb 2026 17:27:45 -0500 Subject: [PATCH] fix(security): deny tool calls requiring approval on remote channels Remote channels (Telegram, Discord, Slack, etc.) previously either auto-approved tool calls requiring approval (in the else-branch) or bypassed the approval check entirely (by passing None for the ApprovalManager). Both paths allowed unapproved tool execution. This fix: - Wires ApprovalManager into ChannelRuntimeContext so remote channels actually enter the approval check - Changes the non-CLI branch from auto-approve to deny-by-default - Adds a tracing::warn log and descriptive error message guiding users to configure auto_approve or set autonomy level to Full - Updates stale doc comment on prompt_cli Co-authored-by: xj --- src/agent/loop_.rs | 17 ++++++++++++++++- src/approval/mod.rs | 5 +++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/agent/loop_.rs b/src/agent/loop_.rs index fac69445e..9c97a201f 100644 --- a/src/agent/loop_.rs +++ b/src/agent/loop_.rs @@ -1308,6 +1308,8 @@ pub(crate) async fn run_tool_call_loop( arguments: tool_args.clone(), }; + // Only prompt interactively on CLI; deny on other channels + // (remote channels cannot provide interactive approval). let decision = if channel_name == "cli" { mgr.prompt_cli(&request) } else if let Some(ctx) = non_cli_approval_context.as_ref() { @@ -1338,13 +1340,26 @@ pub(crate) async fn run_tool_call_loop( ) .await } else { + tracing::warn!( + tool = %tool_name, + channel = %channel_name, + "Tool requires approval but channel cannot prompt — denied" + ); ApprovalResponse::No }; mgr.record_decision(&tool_name, &tool_args, decision, channel_name); if decision == ApprovalResponse::No { - let denied = "Denied by user.".to_string(); + let denied = if channel_name == "cli" { + "Denied by user.".to_string() + } else { + format!( + "Tool '{}' requires approval but channel '{}' cannot prompt interactively. \ + Configure auto_approve or set autonomy level to Full to allow.", + tool_name, channel_name + ) + }; runtime_trace::record_event( "tool_call_result", Some(channel_name), diff --git a/src/approval/mod.rs b/src/approval/mod.rs index bb4076eed..3c49af902 100644 --- a/src/approval/mod.rs +++ b/src/approval/mod.rs @@ -581,8 +581,9 @@ impl ApprovalManager { /// Prompt the user on the CLI and return their decision. /// - /// For non-CLI channels, returns `Yes` automatically (interactive - /// approval is only supported on CLI for now). + /// Only valid for the CLI channel. Non-CLI channels should not call + /// this method; the caller in `run_tool_call_loop` denies by default + /// when the channel cannot provide interactive approval. pub fn prompt_cli(&self, request: &ApprovalRequest) -> ApprovalResponse { prompt_cli_interactive(request) }