fix(reliability): validate fallback API key mapping

This commit is contained in:
argenis de la rosa 2026-03-05 02:07:31 -05:00 committed by Argenis
parent 44ef09da9b
commit 39f2d9dd44
3 changed files with 59 additions and 1 deletions

View File

@ -5952,6 +5952,29 @@ impl Config {
anyhow::bail!("gateway.host must not be empty");
}
// Reliability
let configured_fallbacks = self
.reliability
.fallback_providers
.iter()
.map(|provider| provider.trim())
.filter(|provider| !provider.is_empty())
.collect::<std::collections::HashSet<_>>();
for (entry, api_key) in &self.reliability.fallback_api_keys {
let normalized_entry = entry.trim();
if normalized_entry.is_empty() {
anyhow::bail!("reliability.fallback_api_keys contains an empty key");
}
if api_key.trim().is_empty() {
anyhow::bail!("reliability.fallback_api_keys.{normalized_entry} must not be empty");
}
if !configured_fallbacks.contains(normalized_entry) {
anyhow::bail!(
"reliability.fallback_api_keys.{normalized_entry} has no matching entry in reliability.fallback_providers"
);
}
}
// Autonomy
if self.autonomy.max_actions_per_hour == 0 {
anyhow::bail!("autonomy.max_actions_per_hour must be greater than 0");
@ -10412,6 +10435,40 @@ baseline_syscalls = ["read", "write", "openat", "close"]
assert!(err.to_string().contains("gated_domains"));
}
#[test]
async fn reliability_validation_rejects_empty_fallback_api_key_value() {
let mut config = Config::default();
config.reliability.fallback_providers = vec!["openrouter".to_string()];
config
.reliability
.fallback_api_keys
.insert("openrouter".to_string(), " ".to_string());
let err = config
.validate()
.expect_err("expected fallback_api_keys empty value validation failure");
assert!(err
.to_string()
.contains("reliability.fallback_api_keys.openrouter must not be empty"));
}
#[test]
async fn reliability_validation_rejects_unmapped_fallback_api_key_entry() {
let mut config = Config::default();
config.reliability.fallback_providers = vec!["openrouter".to_string()];
config
.reliability
.fallback_api_keys
.insert("anthropic".to_string(), "sk-ant-test".to_string());
let err = config
.validate()
.expect_err("expected fallback_api_keys mapping validation failure");
assert!(err
.to_string()
.contains("reliability.fallback_api_keys.anthropic has no matching entry"));
}
#[test]
async fn security_validation_rejects_unknown_domain_category() {
let mut config = Config::default();

View File

@ -1442,7 +1442,7 @@ pub fn create_resilient_provider_with_options(
Ok(provider) => providers.push((fallback.clone(), provider)),
Err(_error) => {
tracing::warn!(
fallback_provider = fallback,
fallback_provider = provider_name,
"Ignoring invalid fallback provider during initialization"
);
}

View File

@ -90,6 +90,7 @@ async fn fallback_api_keys_support_multiple_custom_endpoints() {
assert_eq!(reply, "response-from-fallback-two");
primary_server.verify().await;
fallback_server_one.verify().await;
fallback_server_two.verify().await;
}