fix(security): remediate unassigned CodeQL findings
- harden URL/request handling for composio and whatsapp integrations - reduce cleartext logging exposure across providers/tools/gateway - hash and constant-time compare gateway webhook secrets - expand nested secret encryption coverage in config - align feature aliases and add regression tests for security paths - fix bubblewrap all-features test invocation surfaced during deep validation
This commit is contained in:
parent
f9d681063d
commit
1711f140be
14 changed files with 481 additions and 146 deletions
|
|
@ -112,12 +112,12 @@ impl ComposioTool {
|
|||
action_name: &str,
|
||||
params: serde_json::Value,
|
||||
entity_id: Option<&str>,
|
||||
connected_account_id: Option<&str>,
|
||||
connected_account_ref: Option<&str>,
|
||||
) -> anyhow::Result<serde_json::Value> {
|
||||
let tool_slug = normalize_tool_slug(action_name);
|
||||
|
||||
match self
|
||||
.execute_action_v3(&tool_slug, params.clone(), entity_id, connected_account_id)
|
||||
.execute_action_v3(&tool_slug, params.clone(), entity_id, connected_account_ref)
|
||||
.await
|
||||
{
|
||||
Ok(result) => Ok(result),
|
||||
|
|
@ -130,21 +130,16 @@ impl ComposioTool {
|
|||
}
|
||||
}
|
||||
|
||||
async fn execute_action_v3(
|
||||
&self,
|
||||
fn build_execute_action_v3_request(
|
||||
tool_slug: &str,
|
||||
params: serde_json::Value,
|
||||
entity_id: Option<&str>,
|
||||
connected_account_id: Option<&str>,
|
||||
) -> anyhow::Result<serde_json::Value> {
|
||||
let url = if let Some(connected_account_id) = connected_account_id
|
||||
connected_account_ref: Option<&str>,
|
||||
) -> (String, serde_json::Value) {
|
||||
let url = format!("{COMPOSIO_API_BASE_V3}/tools/{tool_slug}/execute");
|
||||
let account_ref = connected_account_ref
|
||||
.map(str::trim)
|
||||
.filter(|id| !id.is_empty())
|
||||
{
|
||||
format!("{COMPOSIO_API_BASE_V3}/tools/{tool_slug}/execute/{connected_account_id}")
|
||||
} else {
|
||||
format!("{COMPOSIO_API_BASE_V3}/tools/{tool_slug}/execute")
|
||||
};
|
||||
.filter(|id| !id.is_empty());
|
||||
|
||||
let mut body = json!({
|
||||
"arguments": params,
|
||||
|
|
@ -153,6 +148,26 @@ impl ComposioTool {
|
|||
if let Some(entity) = entity_id {
|
||||
body["user_id"] = json!(entity);
|
||||
}
|
||||
if let Some(account_ref) = account_ref {
|
||||
body["connected_account_id"] = json!(account_ref);
|
||||
}
|
||||
|
||||
(url, body)
|
||||
}
|
||||
|
||||
async fn execute_action_v3(
|
||||
&self,
|
||||
tool_slug: &str,
|
||||
params: serde_json::Value,
|
||||
entity_id: Option<&str>,
|
||||
connected_account_ref: Option<&str>,
|
||||
) -> anyhow::Result<serde_json::Value> {
|
||||
let (url, body) = Self::build_execute_action_v3_request(
|
||||
tool_slug,
|
||||
params,
|
||||
entity_id,
|
||||
connected_account_ref,
|
||||
);
|
||||
|
||||
let resp = self
|
||||
.client
|
||||
|
|
@ -474,11 +489,11 @@ impl Tool for ComposioTool {
|
|||
})?;
|
||||
|
||||
let params = args.get("params").cloned().unwrap_or(json!({}));
|
||||
let connected_account_id =
|
||||
let connected_account_ref =
|
||||
args.get("connected_account_id").and_then(|v| v.as_str());
|
||||
|
||||
match self
|
||||
.execute_action(action_name, params, Some(entity_id), connected_account_id)
|
||||
.execute_action(action_name, params, Some(entity_id), connected_account_ref)
|
||||
.await
|
||||
{
|
||||
Ok(result) => {
|
||||
|
|
@ -948,4 +963,40 @@ mod tests {
|
|||
fn composio_api_base_url_is_v3() {
|
||||
assert_eq!(COMPOSIO_API_BASE_V3, "https://backend.composio.dev/api/v3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_execute_action_v3_request_uses_fixed_endpoint_and_body_account_id() {
|
||||
let (url, body) = ComposioTool::build_execute_action_v3_request(
|
||||
"gmail-send-email",
|
||||
json!({"to": "test@example.com"}),
|
||||
Some("workspace-user"),
|
||||
Some("account-42"),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
url,
|
||||
"https://backend.composio.dev/api/v3/tools/gmail-send-email/execute"
|
||||
);
|
||||
assert_eq!(body["arguments"]["to"], json!("test@example.com"));
|
||||
assert_eq!(body["user_id"], json!("workspace-user"));
|
||||
assert_eq!(body["connected_account_id"], json!("account-42"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_execute_action_v3_request_drops_blank_optional_fields() {
|
||||
let (url, body) = ComposioTool::build_execute_action_v3_request(
|
||||
"github-list-repos",
|
||||
json!({}),
|
||||
None,
|
||||
Some(" "),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
url,
|
||||
"https://backend.composio.dev/api/v3/tools/github-list-repos/execute"
|
||||
);
|
||||
assert_eq!(body["arguments"], json!({}));
|
||||
assert!(body.get("connected_account_id").is_none());
|
||||
assert!(body.get("user_id").is_none());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ const DELEGATE_TIMEOUT_SECS: u64 = 120;
|
|||
/// summarization) to purpose-built sub-agents.
|
||||
pub struct DelegateTool {
|
||||
agents: Arc<HashMap<String, DelegateAgentConfig>>,
|
||||
/// Global API key fallback (from config.api_key)
|
||||
fallback_api_key: Option<String>,
|
||||
/// Global credential fallback (from config.api_key)
|
||||
fallback_credential: Option<String>,
|
||||
/// Depth at which this tool instance lives in the delegation chain.
|
||||
depth: u32,
|
||||
}
|
||||
|
|
@ -25,11 +25,11 @@ pub struct DelegateTool {
|
|||
impl DelegateTool {
|
||||
pub fn new(
|
||||
agents: HashMap<String, DelegateAgentConfig>,
|
||||
fallback_api_key: Option<String>,
|
||||
fallback_credential: Option<String>,
|
||||
) -> Self {
|
||||
Self {
|
||||
agents: Arc::new(agents),
|
||||
fallback_api_key,
|
||||
fallback_credential,
|
||||
depth: 0,
|
||||
}
|
||||
}
|
||||
|
|
@ -39,12 +39,12 @@ impl DelegateTool {
|
|||
/// their DelegateTool via this method with `depth: parent.depth + 1`.
|
||||
pub fn with_depth(
|
||||
agents: HashMap<String, DelegateAgentConfig>,
|
||||
fallback_api_key: Option<String>,
|
||||
fallback_credential: Option<String>,
|
||||
depth: u32,
|
||||
) -> Self {
|
||||
Self {
|
||||
agents: Arc::new(agents),
|
||||
fallback_api_key,
|
||||
fallback_credential,
|
||||
depth,
|
||||
}
|
||||
}
|
||||
|
|
@ -165,13 +165,13 @@ impl Tool for DelegateTool {
|
|||
}
|
||||
|
||||
// Create provider for this agent
|
||||
let api_key = agent_config
|
||||
let provider_credential = agent_config
|
||||
.api_key
|
||||
.as_deref()
|
||||
.or(self.fallback_api_key.as_deref());
|
||||
.or(self.fallback_credential.as_deref());
|
||||
|
||||
let provider: Box<dyn Provider> =
|
||||
match providers::create_provider(&agent_config.provider, api_key) {
|
||||
match providers::create_provider(&agent_config.provider, provider_credential) {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
return Ok(ToolResult {
|
||||
|
|
@ -268,7 +268,7 @@ mod tests {
|
|||
provider: "openrouter".to_string(),
|
||||
model: "anthropic/claude-sonnet-4-20250514".to_string(),
|
||||
system_prompt: None,
|
||||
api_key: Some("sk-test".to_string()),
|
||||
api_key: Some("delegate-test-credential".to_string()),
|
||||
temperature: None,
|
||||
max_depth: 2,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -440,7 +440,7 @@ mod tests {
|
|||
&http,
|
||||
tmp.path(),
|
||||
&agents,
|
||||
Some("sk-test"),
|
||||
Some("delegate-test-credential"),
|
||||
&cfg,
|
||||
);
|
||||
let names: Vec<&str> = tools.iter().map(|t| t.name()).collect();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue