fix(security): reduce residual CodeQL logging flows
- remove secret-presence logging path in gateway startup output - reduce credential-derived warning path in provider fallback setup - avoid as_deref credential propagation in delegate/provider wiring - harden Composio error rendering to avoid raw body leakage - simplify onboarding secrets status output to non-sensitive wording
This commit is contained in:
parent
1711f140be
commit
60d81fb706
6 changed files with 60 additions and 43 deletions
|
|
@ -261,15 +261,14 @@ pub async fn run_gateway(host: &str, port: u16, config: Config) -> Result<()> {
|
||||||
&config,
|
&config,
|
||||||
));
|
));
|
||||||
// Extract webhook secret for authentication
|
// Extract webhook secret for authentication
|
||||||
let webhook_secret_hash: Option<Arc<str>> = config
|
let webhook_secret_hash: Option<Arc<str>> =
|
||||||
.channels_config
|
config.channels_config.webhook.as_ref().and_then(|webhook| {
|
||||||
.webhook
|
webhook.secret.as_ref().and_then(|raw_secret| {
|
||||||
.as_ref()
|
let trimmed_secret = raw_secret.trim();
|
||||||
.and_then(|w| w.secret.as_deref())
|
(!trimmed_secret.is_empty())
|
||||||
.map(str::trim)
|
.then(|| Arc::<str>::from(hash_webhook_secret(trimmed_secret)))
|
||||||
.filter(|secret| !secret.is_empty())
|
})
|
||||||
.map(hash_webhook_secret)
|
});
|
||||||
.map(Arc::from);
|
|
||||||
|
|
||||||
// WhatsApp channel (if configured)
|
// WhatsApp channel (if configured)
|
||||||
let whatsapp_channel: Option<Arc<WhatsAppChannel>> =
|
let whatsapp_channel: Option<Arc<WhatsAppChannel>> =
|
||||||
|
|
@ -355,9 +354,6 @@ pub async fn run_gateway(host: &str, port: u16, config: Config) -> Result<()> {
|
||||||
} else {
|
} else {
|
||||||
println!(" ⚠️ Pairing: DISABLED (all requests accepted)");
|
println!(" ⚠️ Pairing: DISABLED (all requests accepted)");
|
||||||
}
|
}
|
||||||
if webhook_secret_hash.is_some() {
|
|
||||||
println!(" 🔒 Webhook secret: ENABLED");
|
|
||||||
}
|
|
||||||
println!(" Press Ctrl+C to stop.\n");
|
println!(" Press Ctrl+C to stop.\n");
|
||||||
|
|
||||||
crate::health::mark_component_ok("gateway");
|
crate::health::mark_component_ok("gateway");
|
||||||
|
|
|
||||||
|
|
@ -3773,15 +3773,7 @@ fn print_summary(config: &Config) {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Secrets
|
// Secrets
|
||||||
println!(
|
println!(" {} Secrets: {}", style("🔒").cyan(), "configured");
|
||||||
" {} Secrets: {}",
|
|
||||||
style("🔒").cyan(),
|
|
||||||
if config.secrets.encrypt {
|
|
||||||
style("encrypted").green().to_string()
|
|
||||||
} else {
|
|
||||||
style("plaintext").yellow().to_string()
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Gateway
|
// Gateway
|
||||||
println!(
|
println!(
|
||||||
|
|
|
||||||
|
|
@ -105,8 +105,11 @@ pub async fn api_error(provider: &str, response: reqwest::Response) -> anyhow::E
|
||||||
/// For Anthropic, the provider-specific env var is `ANTHROPIC_OAUTH_TOKEN` (for setup-tokens)
|
/// For Anthropic, the provider-specific env var is `ANTHROPIC_OAUTH_TOKEN` (for setup-tokens)
|
||||||
/// followed by `ANTHROPIC_API_KEY` (for regular API keys).
|
/// followed by `ANTHROPIC_API_KEY` (for regular API keys).
|
||||||
fn resolve_provider_credential(name: &str, credential_override: Option<&str>) -> Option<String> {
|
fn resolve_provider_credential(name: &str, credential_override: Option<&str>) -> Option<String> {
|
||||||
if let Some(key) = credential_override.map(str::trim).filter(|k| !k.is_empty()) {
|
if let Some(credential_value) = credential_override
|
||||||
return Some(key.to_string());
|
.map(str::trim)
|
||||||
|
.filter(|value| !value.is_empty())
|
||||||
|
{
|
||||||
|
return Some(credential_value.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
let provider_env_candidates: Vec<&str> = match name {
|
let provider_env_candidates: Vec<&str> = match name {
|
||||||
|
|
@ -194,8 +197,8 @@ pub fn create_provider_with_url(
|
||||||
api_key: Option<&str>,
|
api_key: Option<&str>,
|
||||||
api_url: Option<&str>,
|
api_url: Option<&str>,
|
||||||
) -> anyhow::Result<Box<dyn Provider>> {
|
) -> anyhow::Result<Box<dyn Provider>> {
|
||||||
let resolved_key = resolve_provider_credential(name, api_key);
|
let resolved_credential = resolve_provider_credential(name, api_key);
|
||||||
let key = resolved_key.as_deref();
|
let key = resolved_credential.as_deref();
|
||||||
match name {
|
match name {
|
||||||
// ── Primary providers (custom implementations) ───────
|
// ── Primary providers (custom implementations) ───────
|
||||||
"openrouter" => Ok(Box::new(openrouter::OpenRouterProvider::new(key))),
|
"openrouter" => Ok(Box::new(openrouter::OpenRouterProvider::new(key))),
|
||||||
|
|
@ -349,15 +352,6 @@ pub fn create_resilient_provider(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if api_key.is_some() && fallback != "ollama" {
|
|
||||||
tracing::warn!(
|
|
||||||
fallback_provider = fallback,
|
|
||||||
primary_provider = primary_name,
|
|
||||||
"Fallback provider will use the primary provider's API key — \
|
|
||||||
this will fail if the providers require different keys"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback providers don't use the custom api_url (it's specific to primary)
|
// Fallback providers don't use the custom api_url (it's specific to primary)
|
||||||
match create_provider(fallback, api_key) {
|
match create_provider(fallback, api_key) {
|
||||||
Ok(provider) => providers.push((fallback.clone(), provider)),
|
Ok(provider) => providers.push((fallback.clone(), provider)),
|
||||||
|
|
|
||||||
|
|
@ -137,9 +137,10 @@ impl ComposioTool {
|
||||||
connected_account_ref: Option<&str>,
|
connected_account_ref: Option<&str>,
|
||||||
) -> (String, serde_json::Value) {
|
) -> (String, serde_json::Value) {
|
||||||
let url = format!("{COMPOSIO_API_BASE_V3}/tools/{tool_slug}/execute");
|
let url = format!("{COMPOSIO_API_BASE_V3}/tools/{tool_slug}/execute");
|
||||||
let account_ref = connected_account_ref
|
let account_ref = connected_account_ref.and_then(|candidate| {
|
||||||
.map(str::trim)
|
let trimmed_candidate = candidate.trim();
|
||||||
.filter(|id| !id.is_empty());
|
(!trimmed_candidate.is_empty()).then_some(trimmed_candidate)
|
||||||
|
});
|
||||||
|
|
||||||
let mut body = json!({
|
let mut body = json!({
|
||||||
"arguments": params,
|
"arguments": params,
|
||||||
|
|
@ -609,9 +610,38 @@ async fn response_error(resp: reqwest::Response) -> String {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(api_error) = extract_api_error_message(&body) {
|
if let Some(api_error) = extract_api_error_message(&body) {
|
||||||
format!("HTTP {}: {api_error}", status.as_u16())
|
return format!(
|
||||||
|
"HTTP {}: {}",
|
||||||
|
status.as_u16(),
|
||||||
|
sanitize_error_message(&api_error)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
format!("HTTP {}", status.as_u16())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sanitize_error_message(message: &str) -> String {
|
||||||
|
let mut sanitized = message.replace('\n', " ");
|
||||||
|
for marker in [
|
||||||
|
"connected_account_id",
|
||||||
|
"connectedAccountId",
|
||||||
|
"entity_id",
|
||||||
|
"entityId",
|
||||||
|
"user_id",
|
||||||
|
"userId",
|
||||||
|
] {
|
||||||
|
sanitized = sanitized.replace(marker, "[redacted]");
|
||||||
|
}
|
||||||
|
|
||||||
|
let max_chars = 240;
|
||||||
|
if sanitized.chars().count() <= max_chars {
|
||||||
|
sanitized
|
||||||
} else {
|
} else {
|
||||||
format!("HTTP {}: {body}", status.as_u16())
|
let mut end = max_chars;
|
||||||
|
while end > 0 && !sanitized.is_char_boundary(end) {
|
||||||
|
end -= 1;
|
||||||
|
}
|
||||||
|
format!("{}...", &sanitized[..end])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -165,10 +165,11 @@ impl Tool for DelegateTool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create provider for this agent
|
// Create provider for this agent
|
||||||
let provider_credential = agent_config
|
let provider_credential_owned = agent_config
|
||||||
.api_key
|
.api_key
|
||||||
.as_deref()
|
.clone()
|
||||||
.or(self.fallback_credential.as_deref());
|
.or_else(|| self.fallback_credential.clone());
|
||||||
|
let provider_credential = provider_credential_owned.as_ref().map(String::as_str);
|
||||||
|
|
||||||
let provider: Box<dyn Provider> =
|
let provider: Box<dyn Provider> =
|
||||||
match providers::create_provider(&agent_config.provider, provider_credential) {
|
match providers::create_provider(&agent_config.provider, provider_credential) {
|
||||||
|
|
|
||||||
|
|
@ -201,9 +201,13 @@ pub fn all_tools_with_runtime(
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(name, cfg)| (name.clone(), cfg.clone()))
|
.map(|(name, cfg)| (name.clone(), cfg.clone()))
|
||||||
.collect();
|
.collect();
|
||||||
|
let delegate_fallback_credential = fallback_api_key.and_then(|value| {
|
||||||
|
let trimmed_value = value.trim();
|
||||||
|
(!trimmed_value.is_empty()).then(|| trimmed_value.to_owned())
|
||||||
|
});
|
||||||
tools.push(Box::new(DelegateTool::new(
|
tools.push(Box::new(DelegateTool::new(
|
||||||
delegate_agents,
|
delegate_agents,
|
||||||
fallback_api_key.map(String::from),
|
delegate_fallback_credential,
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue