Rust reqwest Accept Invalid Certificates Vulnerability

High Risk Insecure Transport
RustreqwestTLSCertificate ValidationMITMTransport Security

What it is

Application configures reqwest HTTP client to accept invalid TLS certificates, making connections vulnerable to man-in-the-middle attacks and certificate spoofing.

use reqwest; use tokio; #[tokio::main] async fn main() -> Result<(), reqwest::Error> { // Vulnerable: Accept invalid certificates let client = reqwest::Client::builder() .danger_accept_invalid_certs(true) // DANGEROUS .build()?; let response = client .get("https://api.example.com/data") .send() .await?; println!("Response: {}", response.text().await?); Ok(()) } // Vulnerable: Global client with disabled verification lazy_static! { static ref HTTP_CLIENT: reqwest::Client = reqwest::Client::builder() .danger_accept_invalid_certs(true) // All requests vulnerable .build() .unwrap(); } pub async fn api_call(url: &str) -> Result { // Uses insecure global client let response = HTTP_CLIENT.get(url).send().await?; response.text().await } pub async fn webhook_call(url: &str, data: &str) -> Result<(), reqwest::Error> { // Vulnerable: Ad-hoc client with disabled verification let client = reqwest::Client::builder() .danger_accept_invalid_certs(true) .build()?; client .post(url) .body(data.to_string()) .send() .await?; Ok(()) }
use reqwest; use std::sync::Arc; use tokio; use rustls::{Certificate, ClientConfig, RootCertStore}; use webpki_roots; #[tokio::main] async fn main() -> Result<(), Box> { // Secure: Use default certificate verification let client = reqwest::Client::builder() .timeout(std::time::Duration::from_secs(10)) .build()?; let response = client .get("https://api.example.com/data") .send() .await?; println!("Response: {}", response.text().await?); Ok(()) } // Secure: Global client with proper TLS configuration fn create_secure_client() -> Result { reqwest::Client::builder() .timeout(std::time::Duration::from_secs(30)) .user_agent("SecureApp/1.0") // Certificate verification enabled by default .build() } lazy_static! { static ref HTTP_CLIENT: reqwest::Client = create_secure_client().unwrap(); } pub async fn api_call(url: &str) -> Result> { // Validate URL scheme let parsed_url = url::Url::parse(url)?; if parsed_url.scheme() != "https" { return Err("Only HTTPS URLs are allowed".into()); } // Use secure global client let response = HTTP_CLIENT .get(url) .timeout(std::time::Duration::from_secs(10)) .send() .await?; if !response.status().is_success() { return Err(format!("HTTP error: {}", response.status()).into()); } Ok(response.text().await?) } pub async fn webhook_call(url: &str, data: &str) -> Result<(), Box> { // Validate URL let parsed_url = url::Url::parse(url)?; if parsed_url.scheme() != "https" { return Err("Only HTTPS URLs are allowed for webhooks".into()); } // Allowlist of permitted webhook hosts let allowed_hosts = vec![ "webhook.trusted.com", "api.partner.com", "notify.secure.com", ]; if let Some(host) = parsed_url.host_str() { if !allowed_hosts.contains(&host) { return Err("Webhook host not in allowlist".into()); } } else { return Err("Invalid webhook URL".into()); } // Secure webhook call let response = HTTP_CLIENT .post(url) .header("Content-Type", "application/json") .body(data.to_string()) .timeout(std::time::Duration::from_secs(15)) .send() .await?; if !response.status().is_success() { return Err(format!("Webhook failed: {}", response.status()).into()); } Ok(()) } // Advanced: Custom TLS configuration with certificate pinning pub fn create_pinned_client(expected_cert_der: &[u8]) -> Result> { let mut root_store = RootCertStore::empty(); root_store.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.0.iter().map( |ta| { rustls::OwnedTrustAnchor::from_subject_spki_name_constraints( ta.subject, ta.spki, ta.name_constraints, ) }, )); // Add pinned certificate let pinned_cert = Certificate(expected_cert_der.to_vec()); let config = ClientConfig::builder() .with_safe_defaults() .with_root_certificates(root_store) .with_no_client_auth(); let client = reqwest::Client::builder() .use_preconfigured_tls(config) .timeout(std::time::Duration::from_secs(30)) .build()?; Ok(client) } // Secure configuration with custom CA pub fn create_client_with_custom_ca(ca_cert_pem: &str) -> Result> { let cert = reqwest::Certificate::from_pem(ca_cert_pem.as_bytes())?; let client = reqwest::Client::builder() .add_root_certificate(cert) .timeout(std::time::Duration::from_secs(30)) .build()?; Ok(client) } // Error handling for certificate issues pub async fn secure_api_call_with_retry(url: &str) -> Result> { let mut attempts = 0; let max_attempts = 3; while attempts < max_attempts { match api_call(url).await { Ok(response) => return Ok(response), Err(e) => { if e.to_string().contains("certificate") { // Don't retry certificate errors return Err(e); } attempts += 1; if attempts >= max_attempts { return Err(e); } // Exponential backoff tokio::time::sleep(std::time::Duration::from_millis(100 * 2_u64.pow(attempts))).await; } } } Err("Max retries exceeded".into()) } // Configuration struct for secure HTTP clients #[derive(Debug, Clone)] pub struct SecureHttpConfig { pub timeout: std::time::Duration, pub user_agent: String, pub allowed_hosts: Vec, pub require_https: bool, } impl Default for SecureHttpConfig { fn default() -> Self { Self { timeout: std::time::Duration::from_secs(30), user_agent: "SecureApp/1.0".to_string(), allowed_hosts: vec![], require_https: true, } } } pub struct SecureHttpClient { client: reqwest::Client, config: SecureHttpConfig, } impl SecureHttpClient { pub fn new(config: SecureHttpConfig) -> Result { let client = reqwest::Client::builder() .timeout(config.timeout) .user_agent(&config.user_agent) .build()?; Ok(Self { client, config }) } pub async fn get(&self, url: &str) -> Result> { self.validate_url(url)?; let response = self.client.get(url).send().await?; if !response.status().is_success() { return Err(format!("HTTP error: {}", response.status()).into()); } Ok(response.text().await?) } fn validate_url(&self, url: &str) -> Result<(), Box> { let parsed_url = url::Url::parse(url)?; if self.config.require_https && parsed_url.scheme() != "https" { return Err("Only HTTPS URLs are allowed".into()); } if !self.config.allowed_hosts.is_empty() { if let Some(host) = parsed_url.host_str() { if !self.config.allowed_hosts.contains(&host.to_string()) { return Err("Host not in allowlist".into()); } } } Ok(()) } }

💡 Why This Fix Works

See fix suggestions for detailed explanation.

Why it happens

Rust code disables cert validation: Client::builder().danger_accept_invalid_certs(true).build(). Accepts any TLS certificate including self-signed. Man-in-the-middle attacks possible. HTTPS security completely bypassed. danger_ prefix indicates security risk but sometimes used anyway.

Root causes

Using danger_accept_invalid_certs in reqwest Client

Rust code disables cert validation: Client::builder().danger_accept_invalid_certs(true).build(). Accepts any TLS certificate including self-signed. Man-in-the-middle attacks possible. HTTPS security completely bypassed. danger_ prefix indicates security risk but sometimes used anyway.

Development or Testing Code Left in Production

Debug code with disabled validation: if cfg!(debug_assertions) { builder.danger_accept_invalid_certs(true) }. Development patterns in production builds. Config flags failing. Test code paths active. Temporary debugging solutions becoming permanent.

Working Around Certificate Issues Instead of Fixing

Certificate errors bypassed: self-signed certificates, expired certs, hostname mismatches. danger_accept_invalid_certs quick fix. Root cause not addressed. Proper certificate management avoided. Security sacrificed for convenience.

Not Understanding TLS Certificate Validation Purpose

Developers unaware of validation importance. Believing HTTPS encryption alone sufficient. Not understanding authentication aspect. Missing man-in-the-middle attack knowledge. Encryption without authentication provides no security against active attackers.

Internal Services Assumed Safe Without Certificates

Internal API calls with disabled validation: assuming internal network secure. Lateral movement after initial compromise. Internal services should use TLS same as external. Zero-trust networking requires internal TLS validation.

Fixes

1

Never Use danger_accept_invalid_certs in Production

Remove all danger_accept_invalid_certs: Client::builder().build() defaults to strict validation. Search codebase: grep -r 'danger_accept_invalid_certs' --include="*.rs". Delete all instances. Code review rejecting this method. Certificate validation non-negotiable.

2

Add Custom CA Certificates for Self-Signed Certificates

Use reqwest Certificate: use reqwest::Certificate; let cert = Certificate::from_pem(&cert_bytes)?; Client::builder().add_root_certificate(cert).build()?. Add custom CA to trust store. Proper certificate validation with internal CAs. Maintains security while supporting self-signed.

3

Use Native TLS or Rustls with System Certificates

Default TLS backend uses system certificates: Client::builder().use_native_tls().build() or .use_rustls_tls().build(). System trust store automatically used. Updated with OS certificate updates. Proper CA chain validation. Modern TLS support.

4

Fix Certificate Errors Properly, Never Work Around

Address root causes: renew expired certificates; fix hostname mismatches; install proper CA certificates; use Let's Encrypt for free valid certs. Certificate errors indicate real problems. Security issues require fixing, not ignoring.

5

Use Certificate Pinning for Critical Services

Pin certificates: let cert = Certificate::from_pem(include_bytes!("server.crt"))?; Client::builder().add_root_certificate(cert).https_only(true).build()?. Specific certificate or CA. Prevents man-in-the-middle with compromised CAs. Critical for auth or payment services.

6

Enable HTTPS-Only Mode and Fail on Invalid Certificates

Strict HTTPS: Client::builder().https_only(true).build()?. Force HTTPS scheme. Fail on validation errors. No HTTP fallback. Explicit error handling. Log certificate failures for monitoring. Production should never accept invalid certificates.

Detect This Vulnerability in Your Code

Sourcery automatically identifies rust reqwest accept invalid certificates vulnerability and many other security issues in your codebase.