//! HTTP Client Configuration //! //! **INTENTIONAL VIOLATIONS IN THIS FILE:** //! - VIOLATION 1: Unbounded redirect limit (max_redirects = None) //! - VIOLATION 2: Excessive request timeout (120s vs 30s max) //! - VIOLATION 3: Excessive connection timeout (60s vs 10s max) //! - VIOLATION 4: Missing idle timeout (idle_timeout = None) //! - VIOLATION 5: TLS verification disabled (verify_tls = false) //! - VIOLATION 6: TLS version too low (min_tls_version = TLS 1.0) use serde::{Deserialize, Serialize}; use std::time::Duration; /// TLS protocol version #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] pub enum TlsVersion { #[serde(rename = "1.0")] Tls10, #[serde(rename = "1.1")] Tls11, #[serde(rename = "1.2")] Tls12, #[serde(rename = "1.3")] Tls13, } /// HTTP client configuration #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ClientConfig { // Connection settings /// Maximum number of redirects to follow /// /// # VIOLATION 1: Unbounded Redirect Limit /// @aphoria:claim[safety] Redirect limit MUST NOT exceed 10 -- Infinite redirect loops exhaust resources /// /// **Authority:** RFC 7231 Section 6.4 /// **Current value:** None (unbounded) /// **Should be:** Some(10) #[serde(default)] pub max_redirects: Option, /// Connection timeout (TCP handshake) /// /// # VIOLATION 3: Excessive Connection Timeout /// @aphoria:claim[safety] Connection timeout MUST NOT exceed 10 seconds -- Unresponsive endpoints block connection pool /// /// **Authority:** Mozilla HTTP docs, Requests library /// **Current value:** 60s /// **Should be:** 10s #[serde(default = "default_connect_timeout")] pub connect_timeout: Duration, /// Request timeout (total request/response cycle) /// /// # VIOLATION 2: Excessive Request Timeout /// @aphoria:claim[safety] Request timeout MUST NOT exceed 30 seconds -- Slow services cause cascade failures /// /// **Authority:** Mozilla HTTP docs, RFC 7230 /// **Current value:** 120s /// **Should be:** 30s #[serde(default = "default_request_timeout")] pub request_timeout: Duration, /// Idle connection timeout (keep-alive timeout) /// /// # VIOLATION 4: Missing Idle Timeout /// @aphoria:claim[safety] Idle timeout MUST be configured -- Stale connections accumulate /// /// **Authority:** RFC 7230 Section 6.3 /// **Current value:** None (connections never expire) /// **Should be:** Some(60s) #[serde(default)] pub idle_timeout: Option, // TLS settings /// Enable TLS certificate verification /// /// # VIOLATION 5: TLS Verification Disabled /// @aphoria:claim[security] TLS certificate validation MUST be enabled -- MITM attacks, credential theft /// /// **Authority:** OWASP A07:2021, Mozilla Security Guidelines /// **Current value:** false /// **Should be:** true #[serde(default = "default_verify_tls")] pub verify_tls: bool, /// Minimum TLS version /// /// # VIOLATION 6: TLS Version Too Low /// @aphoria:claim[security] TLS version MUST be >= 1.2 -- Protocol downgrade attacks (BEAST, POODLE) /// /// **Authority:** OWASP, Mozilla Security Guidelines /// **Current value:** TLS 1.0 /// **Should be:** TLS 1.2 #[serde(default = "default_min_tls_version")] pub min_tls_version: TlsVersion, // Pool settings /// Connection pool size per host #[serde(default = "default_pool_size")] pub pool_size: usize, // Observability /// Enable metrics collection #[serde(default = "default_metrics_enabled")] pub metrics_enabled: bool, // User-Agent header #[serde(default = "default_user_agent")] pub user_agent: String, } impl Default for ClientConfig { fn default() -> Self { Self { // VIOLATION 1: Unbounded redirects max_redirects: None, // VIOLATION 3: Excessive connect timeout (60s vs 10s max) connect_timeout: Duration::from_secs(60), // VIOLATION 2: Excessive request timeout (120s vs 30s max) request_timeout: Duration::from_secs(120), // VIOLATION 4: Missing idle timeout idle_timeout: None, // VIOLATION 5: TLS verification disabled verify_tls: false, // VIOLATION 6: TLS version too low (1.0 vs 1.2 minimum) min_tls_version: TlsVersion::Tls10, pool_size: default_pool_size(), metrics_enabled: default_metrics_enabled(), user_agent: default_user_agent(), } } } // Default value functions fn default_connect_timeout() -> Duration { // VIOLATION 3: Should be 10s Duration::from_secs(60) } fn default_request_timeout() -> Duration { // VIOLATION 2: Should be 30s Duration::from_secs(120) } fn default_verify_tls() -> bool { // VIOLATION 5: Should be true false } fn default_min_tls_version() -> TlsVersion { // VIOLATION 6: Should be TLS 1.2 TlsVersion::Tls10 } fn default_pool_size() -> usize { 10 // This is correct (Requests library default) } fn default_metrics_enabled() -> bool { false // Recommended to be true, but not a hard violation } fn default_user_agent() -> String { format!("httpclient/{}", env!("CARGO_PKG_VERSION")) } impl ClientConfig { /// Create a new config with default values (contains violations) pub fn new() -> Self { Self::default() } /// Create a production-safe config (no violations) pub fn production() -> Self { Self { max_redirects: Some(10), // RFC 7231 compliant connect_timeout: Duration::from_secs(10), // Mozilla/Requests default request_timeout: Duration::from_secs(30), // Mozilla recommended idle_timeout: Some(Duration::from_secs(60)), // RFC 7230 keep-alive verify_tls: true, // OWASP A07:2021 min_tls_version: TlsVersion::Tls12, // OWASP/Mozilla minimum pool_size: 50, // Production recommended metrics_enabled: true, // Observability user_agent: default_user_agent(), } } /// Validate configuration against safety claims pub fn validate(&self) -> Result<(), String> { let mut errors = Vec::new(); // Check redirect limit if self.max_redirects.is_none() || self.max_redirects.unwrap() > 10 { errors.push("max_redirects MUST be <= 10 (RFC 7231)"); } // Check timeouts if self.connect_timeout.as_secs() > 10 { errors.push("connect_timeout MUST be <= 10s (Mozilla/Requests)"); } if self.request_timeout.as_secs() > 30 { errors.push("request_timeout MUST be <= 30s (Mozilla/RFC 7230)"); } if self.idle_timeout.is_none() { errors.push("idle_timeout MUST be configured (RFC 7230 Section 6.3)"); } // Check TLS if !self.verify_tls { errors.push("verify_tls MUST be true (OWASP A07:2021)"); } if matches!(self.min_tls_version, TlsVersion::Tls10 | TlsVersion::Tls11) { errors.push("min_tls_version MUST be >= 1.2 (OWASP/Mozilla)"); } if errors.is_empty() { Ok(()) } else { Err(errors.join("; ")) } } } #[cfg(test)] mod tests { use super::*; #[test] fn default_config_has_violations() { let config = ClientConfig::default(); assert!(config.validate().is_err(), "Default config should have violations"); } #[test] fn production_config_is_valid() { let config = ClientConfig::production(); assert!(config.validate().is_ok(), "Production config should be valid"); } #[test] fn violation_1_unbounded_redirects() { let config = ClientConfig::default(); assert_eq!(config.max_redirects, None, "VIOLATION 1: Unbounded redirects"); } #[test] fn violation_2_excessive_request_timeout() { let config = ClientConfig::default(); assert_eq!(config.request_timeout.as_secs(), 120, "VIOLATION 2: 120s request timeout"); } #[test] fn violation_3_excessive_connect_timeout() { let config = ClientConfig::default(); assert_eq!(config.connect_timeout.as_secs(), 60, "VIOLATION 3: 60s connect timeout"); } #[test] fn violation_4_missing_idle_timeout() { let config = ClientConfig::default(); assert_eq!(config.idle_timeout, None, "VIOLATION 4: Missing idle timeout"); } #[test] fn violation_5_tls_verification_disabled() { let config = ClientConfig::default(); assert!(!config.verify_tls, "VIOLATION 5: TLS verification disabled"); } #[test] fn violation_6_tls_version_too_low() { let config = ClientConfig::default(); assert_eq!(config.min_tls_version, TlsVersion::Tls10, "VIOLATION 6: TLS 1.0"); } }