From 997be4eef227f13e0350072c9ad1d83ecd3d8a6a Mon Sep 17 00:00:00 2001 From: sanil-23 Date: Wed, 20 May 2026 18:42:01 +0530 Subject: [PATCH] fix(composio): trim API key in ComposioTool constructor (#2323) (#2338) Co-authored-by: sanil-23 Co-authored-by: Claude --- src/openhuman/tools/impl/network/composio.rs | 16 ++++++++- .../tools/impl/network/composio_tests.rs | 35 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/openhuman/tools/impl/network/composio.rs b/src/openhuman/tools/impl/network/composio.rs index b015e74c2..26fc49013 100644 --- a/src/openhuman/tools/impl/network/composio.rs +++ b/src/openhuman/tools/impl/network/composio.rs @@ -41,8 +41,22 @@ impl ComposioTool { default_entity_id: Option<&str>, security: Arc, ) -> Self { + let trimmed = api_key.trim(); + if trimmed.len() != api_key.len() { + // The key carried leading/trailing whitespace that would otherwise + // reach Composio's `x-api-key` header verbatim and trip the + // server-side "Invalid API key format" 401 (Sentry TAURI-RUST-D3). + // We trim here so the request succeeds; logging the length delta + // (never the key itself) helps trace which credential source + // produced a dirty value without leaking the secret. + tracing::debug!( + original_len = api_key.len(), + trimmed_len = trimmed.len(), + "[composio] trimmed leading/trailing whitespace from api_key" + ); + } Self { - api_key: api_key.to_string(), + api_key: trimmed.to_string(), default_entity_id: normalize_entity_id(default_entity_id.unwrap_or("default")), security, } diff --git a/src/openhuman/tools/impl/network/composio_tests.rs b/src/openhuman/tools/impl/network/composio_tests.rs index 47ab6705b..be6dbe455 100644 --- a/src/openhuman/tools/impl/network/composio_tests.rs +++ b/src/openhuman/tools/impl/network/composio_tests.rs @@ -574,3 +574,38 @@ fn connected_account_accepts_camelcase_created_at() { .unwrap(); assert_eq!(raw.created_at.as_deref(), Some("2026-05-15T00:00:00Z")); } + +// ── API key trimming (issue #2323) ──────────────────────── +// +// Composio v3 rejects API keys with leading/trailing whitespace as +// "Invalid API key format" (Sentry TAURI-RUST-D3). The constructor must +// strip surrounding whitespace defensively, but MUST preserve internal +// whitespace so legitimate keys containing spaces are not corrupted. + +#[test] +fn composio_tool_trims_surrounding_whitespace_in_api_key() { + let tool = ComposioTool::new(" key123 ", None, test_security()); + assert_eq!(tool.api_key, "key123"); +} + +#[test] +fn composio_tool_trims_trailing_newline_in_api_key() { + // The real-world Sentry case: secret store payloads frequently carry a + // trailing newline (clipboard paste, file read). It must be stripped. + let tool = ComposioTool::new("key123\n", None, test_security()); + assert_eq!(tool.api_key, "key123"); +} + +#[test] +fn composio_tool_preserves_internal_whitespace_in_api_key() { + // Pins the trim-scope: a future refactor must NOT widen this to + // `replace(' ', "")` or similar — only surrounding whitespace is stripped. + let tool = ComposioTool::new("k1 k2", None, test_security()); + assert_eq!(tool.api_key, "k1 k2"); +} + +#[test] +fn composio_tool_accepts_empty_api_key_without_panic() { + let tool = ComposioTool::new("", None, test_security()); + assert_eq!(tool.api_key, ""); +}