Configuration may unexpectedly disable certificate validation
Description
When tlsInsecure=False appears in a connection string, certificate validation is disabled.
This vulnerability affects MongoDB Rust Driver versions prior to v3.2.5
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
MongoDB Rust Driver ignores tlsInsecure=false in connection strings, disabling TLS certificate validation and enabling man-in-the-middle attacks.
Vulnerability
Overview
The MongoDB Rust Driver incorrectly handles the tlsInsecure URI option. When a connection string includes tlsInsecure=False, the driver does not properly enforce certificate validation, effectively disabling TLS security. This bug exists in all versions prior to v3.2.5 [1][2].
Attack
Vector
An attacker can exploit this by intercepting or redirecting a client's connection to a malicious MongoDB server. Since the driver fails to validate certificates even when tlsInsecure=False is explicitly set, the client will not detect the imposter server. The attack requires the ability to perform a man-in-the-middle (MITM) attack on the network path between the client and the intended MongoDB deployment [3][4].
Impact
Successful exploitation allows the attacker to decrypt, modify, or inject data in transit, compromising the confidentiality and integrity of all communication between the client and database. The vulnerability undermines the TLS security guarantees that users expect when setting tlsInsecure=False (which should enforce certificate validation) [1][2].
Mitigation
The issue is fixed in the MongoDB Rust Driver version 3.2.5 [2]. Users are advised to upgrade to v3.2.5 or later. The fix ensures that tlsInsecure=False properly enables certificate validation by parsing the URI option correctly and applying it to TLS options [3][4].
AI Insight generated on May 19, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
mongodbcrates.io | < 3.2.5 | 3.2.5 |
Affected products
2- Range: <3.2.5
- MongoDB/Rust Driverv5Range: 0
Patches
221ed6aeeea38RUST-2265: cherrypick #1453 for 3.2.5 (#1455)
4 files changed · +140 −76
src/action/client_options.rs+4 −4 modified@@ -51,10 +51,10 @@ impl ClientOptions { /// * `socketTimeoutMS`: unsupported, does not map to any field /// * `ssl`: an alias of the `tls` option /// * `tls`: maps to the TLS variant of the `tls` field`. - /// * `tlsInsecure`: relaxes the TLS constraints on connections being made; currently is just - /// an alias of `tlsAllowInvalidCertificates`, but more behavior may be added to this option - /// in the future - /// * `tlsAllowInvalidCertificates`: maps to the `allow_invalidCertificates` field of the + /// * `tlsInsecure`: relaxes the TLS constraints on connections being made; if set, the + /// `allow_invalid_certificates` and `allow_invalid_hostnames` fields of the `tls` field are + /// set to its value + /// * `tlsAllowInvalidCertificates`: maps to the `allow_invalid_certificates` field of the /// `tls` field /// * `tlsCAFile`: maps to the `ca_file_path` field of the `tls` field /// * `tlsCertificateKeyFile`: maps to the `cert_key_file_path` field of the `tls` field
src/action/insert_one.rs+1 −1 modified@@ -59,7 +59,7 @@ impl<T: Serialize + Send + Sync> crate::sync::Collection<T> { } } -/// Inserts a document into a collection. Construct with ['Collection::insert_one`]. +/// Inserts a document into a collection. Construct with [`Collection::insert_one`]. #[must_use] pub struct InsertOne<'a> { coll: CollRef<'a>,
src/client/options.rs+82 −71 modified@@ -55,6 +55,10 @@ pub(crate) use resolver_config::ResolverConfig; pub(crate) const DEFAULT_PORT: u16 = 27017; +const TLS_INSECURE: &str = "tlsinsecure"; +const TLS_ALLOW_INVALID_CERTIFICATES: &str = "tlsallowinvalidcertificates"; +#[cfg(feature = "openssl-tls")] +const TLS_ALLOW_INVALID_HOSTNAMES: &str = "tlsallowinvalidhostnames"; const URI_OPTIONS: &[&str] = &[ "appname", "authmechanism", @@ -82,8 +86,8 @@ const URI_OPTIONS: &[&str] = &[ "sockettimeoutms", "tls", "ssl", - "tlsinsecure", - "tlsallowinvalidcertificates", + TLS_INSECURE, + TLS_ALLOW_INVALID_CERTIFICATES, "tlscafile", "tlscertificatekeyfile", "uuidRepresentation", @@ -1646,7 +1650,9 @@ impl ConnectionString { } /// Relax TLS constraints as much as possible (e.g. allowing invalid certificates or hostname - /// mismatches). Not supported by the Rust driver. + /// mismatches). This option can only be set in a URI. If it is set in a URI provided to + /// [`ConnectionString::parse`], [`TlsOptions::allow_invalid_certificates`] and + /// [`TlsOptions::allow_invalid_hostnames`] are set to its value. pub fn tls_insecure(&self) -> Option<bool> { self.tls_insecure } @@ -1661,42 +1667,47 @@ impl ConnectionString { return Ok(parts); } - let mut keys: Vec<&str> = Vec::new(); + let mut keys = HashSet::new(); for option_pair in options.split('&') { - let (key, value) = match option_pair.find('=') { - Some(index) => option_pair.split_at(index), + let (key, value) = match option_pair.split_once('=') { + Some((key, value)) => (key.to_lowercase(), value), None => { - return Err(ErrorKind::InvalidArgument { - message: format!( - "connection string options is not a `key=value` pair: {}", - option_pair, - ), - } - .into()) + return Err(Error::invalid_argument(format!( + "connection string option is not a 'key=value' pair: {option_pair}" + ))) } }; - if key.to_lowercase() != "readpreferencetags" && keys.contains(&key) { - return Err(ErrorKind::InvalidArgument { - message: "repeated options are not allowed in the connection string" - .to_string(), - } - .into()); - } else { - keys.push(key); + if !keys.insert(key.clone()) && key != "readpreferencetags" { + return Err(Error::invalid_argument( + "repeated options are not allowed in the connection string", + )); } - // Skip leading '=' in value. self.parse_option_pair( &mut parts, - &key.to_lowercase(), - percent_encoding::percent_decode(&value.as_bytes()[1..]) + &key, + percent_encoding::percent_decode(value.as_bytes()) .decode_utf8_lossy() .as_ref(), )?; } + if keys.contains(TLS_INSECURE) { + #[cfg(feature = "openssl-tls")] + let disallowed = [TLS_ALLOW_INVALID_CERTIFICATES, TLS_ALLOW_INVALID_HOSTNAMES]; + #[cfg(not(feature = "openssl-tls"))] + let disallowed = [TLS_ALLOW_INVALID_CERTIFICATES]; + for option in disallowed { + if keys.contains(option) { + return Err(Error::invalid_argument(format!( + "cannot set both {TLS_INSECURE} and {option} in the connection string" + ))); + } + } + } + if let Some(tags) = parts.read_preference_tags.take() { self.read_preference = match self.read_preference.take() { Some(read_pref) => Some(read_pref.with_tags(tags)?), @@ -2042,63 +2053,63 @@ impl ConnectionString { k @ "tls" | k @ "ssl" => { let tls = get_bool!(value, k); - match (self.tls.as_ref(), tls) { - (Some(Tls::Disabled), true) | (Some(Tls::Enabled(..)), false) => { - return Err(ErrorKind::InvalidArgument { - message: "All instances of `tls` and `ssl` must have the same - value" - .to_string(), + match self.tls { + Some(Tls::Enabled(_)) if !tls => { + return Err(Error::invalid_argument( + "cannot set {key}={tls} if other TLS options are set", + )) + } + Some(Tls::Disabled) if tls => { + return Err(Error::invalid_argument( + "cannot set {key}={tls} if TLS is disabled", + )) + } + None => { + if tls { + self.tls = Some(Tls::Enabled(Default::default())) + } else { + self.tls = Some(Tls::Disabled) } - .into()); } _ => {} - }; - - if self.tls.is_none() { - let tls = if tls { - Tls::Enabled(Default::default()) - } else { - Tls::Disabled - }; - - self.tls = Some(tls); } } - k @ "tlsinsecure" | k @ "tlsallowinvalidcertificates" => { - let val = get_bool!(value, k); - - let allow_invalid_certificates = if k == "tlsinsecure" { !val } else { val }; + TLS_INSECURE => { + let val = get_bool!(value, key); + self.tls_insecure = Some(val); - match self.tls { - Some(Tls::Disabled) => { - return Err(ErrorKind::InvalidArgument { - message: "'tlsInsecure' can't be set if tls=false".into(), + match self + .tls + .get_or_insert_with(|| Tls::Enabled(Default::default())) + { + Tls::Enabled(ref mut options) => { + options.allow_invalid_certificates = Some(val); + #[cfg(feature = "openssl-tls")] + { + options.allow_invalid_hostnames = Some(val); } - .into()) } - Some(Tls::Enabled(ref options)) - if options.allow_invalid_certificates.is_some() - && options.allow_invalid_certificates - != Some(allow_invalid_certificates) => - { - return Err(ErrorKind::InvalidArgument { - message: "all instances of 'tlsInsecure' and \ - 'tlsAllowInvalidCertificates' must be consistent (e.g. \ - 'tlsInsecure' cannot be true when \ - 'tlsAllowInvalidCertificates' is false, or vice-versa)" - .into(), - } - .into()); + Tls::Disabled => { + return Err(Error::invalid_argument(format!( + "cannot set {key} when TLS is disabled" + ))); } - Some(Tls::Enabled(ref mut options)) => { - options.allow_invalid_certificates = Some(allow_invalid_certificates); + } + } + TLS_ALLOW_INVALID_CERTIFICATES => { + let val = get_bool!(value, key); + + match self + .tls + .get_or_insert_with(|| Tls::Enabled(Default::default())) + { + Tls::Enabled(ref mut options) => { + options.allow_invalid_certificates = Some(val); } - None => { - self.tls = Some(Tls::Enabled( - TlsOptions::builder() - .allow_invalid_certificates(allow_invalid_certificates) - .build(), - )) + Tls::Disabled => { + return Err(Error::invalid_argument(format!( + "cannot set {key} when TLS is disabled" + ))) } } }
src/client/options/test.rs+53 −0 modified@@ -10,6 +10,7 @@ use crate::{ bson_util::get_int, client::options::{ClientOptions, ConnectionString, ServerAddress}, error::ErrorKind, + options::Tls, test::spec::deserialize_spec_tests, Client, }; @@ -395,3 +396,55 @@ async fn tls_cert_key_password_connect() { .await .unwrap(); } + +#[tokio::test] +async fn tls_insecure() { + let uri = "mongodb://localhost:27017/?tls=true&tlsInsecure=true"; + let options = ClientOptions::parse(uri).await.unwrap(); + let Some(Tls::Enabled(tls_options)) = options.tls else { + panic!("expected tls options to be set"); + }; + assert_eq!(tls_options.allow_invalid_certificates, Some(true)); + #[cfg(feature = "openssl-tls")] + assert_eq!(tls_options.allow_invalid_hostnames, Some(true)); + + let uri = "mongodb://localhost:27017/?tls=true&tlsInsecure=false"; + let options = ClientOptions::parse(uri).await.unwrap(); + let Some(Tls::Enabled(tls_options)) = options.tls else { + panic!("expected tls options to be set"); + }; + assert_eq!(tls_options.allow_invalid_certificates, Some(false)); + #[cfg(feature = "openssl-tls")] + assert_eq!(tls_options.allow_invalid_hostnames, Some(false)); + + let uri = "mongodb://localhost:27017/?tls=false&tlsInsecure=true"; + let error = ClientOptions::parse(uri).await.unwrap_err(); + assert!(error.message().unwrap().contains("TLS is disabled")); + + let uri = "mongodb://localhost:27017/?tlsInsecure=true&tls=false"; + let error = ClientOptions::parse(uri).await.unwrap_err(); + assert!(error + .message() + .unwrap() + .contains("other TLS options are set")); + + let uri = "mongodb://localhost:27017/?tlsInsecure=true&tlsAllowInvalidCertificates=true"; + let error = ClientOptions::parse(uri).await.unwrap_err(); + assert!(error.message().unwrap().contains("cannot set both")); + + let uri = "mongodb://localhost:27017/?tlsInsecure=true&tlsAllowInvalidCertificates=false"; + let error = ClientOptions::parse(uri).await.unwrap_err(); + assert!(error.message().unwrap().contains("cannot set both")); + + // TODO RUST-1896: uncomment these tests + // #[cfg(feature = "openssl-tls")] + // { + // let uri = "mongodb://localhost:27017/?tlsInsecure=true&tlsAllowInvalidHostnames=true"; + // let error = ClientOptions::parse(uri).await.unwrap_err(); + // assert!(error.message().unwrap().contains("cannot set both")); + + // let uri = "mongodb://localhost:27017/?tlsInsecure=true&tlsAllowInvalidHostnames=false"; + // let error = ClientOptions::parse(uri).await.unwrap_err(); + // assert!(error.message().unwrap().contains("cannot set both")); + // } +}
b918cd667633RUST-2264 Fix handling of tlsInsecure in the URI (#1453)
3 files changed · +140 −75
src/action/client_options.rs+4 −4 modified@@ -51,10 +51,10 @@ impl ClientOptions { /// * `socketTimeoutMS`: unsupported, does not map to any field /// * `ssl`: an alias of the `tls` option /// * `tls`: maps to the TLS variant of the `tls` field`. - /// * `tlsInsecure`: relaxes the TLS constraints on connections being made; currently is just - /// an alias of `tlsAllowInvalidCertificates`, but more behavior may be added to this option - /// in the future - /// * `tlsAllowInvalidCertificates`: maps to the `allow_invalidCertificates` field of the + /// * `tlsInsecure`: relaxes the TLS constraints on connections being made; if set, the + /// `allow_invalid_certificates` and `allow_invalid_hostnames` fields of the `tls` field are + /// set to its value + /// * `tlsAllowInvalidCertificates`: maps to the `allow_invalid_certificates` field of the /// `tls` field /// * `tlsCAFile`: maps to the `ca_file_path` field of the `tls` field /// * `tlsCertificateKeyFile`: maps to the `cert_key_file_path` field of the `tls` field
src/client/options.rs+83 −71 modified@@ -55,6 +55,10 @@ pub(crate) use resolver_config::ResolverConfig; pub(crate) const DEFAULT_PORT: u16 = 27017; +const TLS_INSECURE: &str = "tlsinsecure"; +const TLS_ALLOW_INVALID_CERTIFICATES: &str = "tlsallowinvalidcertificates"; +#[cfg(feature = "openssl-tls")] +const TLS_ALLOW_INVALID_HOSTNAMES: &str = "tlsallowinvalidhostnames"; const URI_OPTIONS: &[&str] = &[ "appname", "authmechanism", @@ -82,8 +86,8 @@ const URI_OPTIONS: &[&str] = &[ "sockettimeoutms", "tls", "ssl", - "tlsinsecure", - "tlsallowinvalidcertificates", + TLS_INSECURE, + TLS_ALLOW_INVALID_CERTIFICATES, "tlscafile", "tlscertificatekeyfile", "uuidRepresentation", @@ -1647,7 +1651,9 @@ impl ConnectionString { } /// Relax TLS constraints as much as possible (e.g. allowing invalid certificates or hostname - /// mismatches). Not supported by the Rust driver. + /// mismatches). This option can only be set in a URI. If it is set in a URI provided to + /// [`ConnectionString::parse`], [`TlsOptions::allow_invalid_certificates`] and + /// [`TlsOptions::allow_invalid_hostnames`] are set to its value. pub fn tls_insecure(&self) -> Option<bool> { self.tls_insecure } @@ -1662,41 +1668,47 @@ impl ConnectionString { return Ok(parts); } - let mut keys: Vec<&str> = Vec::new(); + let mut keys = HashSet::new(); for option_pair in options.split('&') { - let (key, value) = match option_pair.find('=') { - Some(index) => option_pair.split_at(index), + let (key, value) = match option_pair.split_once('=') { + Some((key, value)) => (key.to_lowercase(), value), None => { - return Err(ErrorKind::InvalidArgument { - message: format!( - "connection string options is not a `key=value` pair: {option_pair}", - ), - } - .into()) + return Err(Error::invalid_argument(format!( + "connection string option is not a 'key=value' pair: {option_pair}" + ))) } }; - if key.to_lowercase() != "readpreferencetags" && keys.contains(&key) { - return Err(ErrorKind::InvalidArgument { - message: "repeated options are not allowed in the connection string" - .to_string(), - } - .into()); - } else { - keys.push(key); + if !keys.insert(key.clone()) && key != "readpreferencetags" { + return Err(Error::invalid_argument( + "repeated options are not allowed in the connection string", + )); } - // Skip leading '=' in value. self.parse_option_pair( &mut parts, - &key.to_lowercase(), - percent_encoding::percent_decode(&value.as_bytes()[1..]) + &key, + percent_encoding::percent_decode(value.as_bytes()) .decode_utf8_lossy() .as_ref(), )?; } + if keys.contains(TLS_INSECURE) { + #[cfg(feature = "openssl-tls")] + let disallowed = [TLS_ALLOW_INVALID_CERTIFICATES, TLS_ALLOW_INVALID_HOSTNAMES]; + #[cfg(not(feature = "openssl-tls"))] + let disallowed = [TLS_ALLOW_INVALID_CERTIFICATES]; + for option in disallowed { + if keys.contains(option) { + return Err(Error::invalid_argument(format!( + "cannot set both {TLS_INSECURE} and {option} in the connection string" + ))); + } + } + } + if let Some(tags) = parts.read_preference_tags.take() { self.read_preference = match self.read_preference.take() { Some(read_pref) => Some(read_pref.with_tags(tags)?), @@ -2017,63 +2029,63 @@ impl ConnectionString { k @ "tls" | k @ "ssl" => { let tls = get_bool!(value, k); - match (self.tls.as_ref(), tls) { - (Some(Tls::Disabled), true) | (Some(Tls::Enabled(..)), false) => { - return Err(ErrorKind::InvalidArgument { - message: "All instances of `tls` and `ssl` must have the same - value" - .to_string(), + match self.tls { + Some(Tls::Enabled(_)) if !tls => { + return Err(Error::invalid_argument( + "cannot set {key}={tls} if other TLS options are set", + )) + } + Some(Tls::Disabled) if tls => { + return Err(Error::invalid_argument( + "cannot set {key}={tls} if TLS is disabled", + )) + } + None => { + if tls { + self.tls = Some(Tls::Enabled(Default::default())) + } else { + self.tls = Some(Tls::Disabled) } - .into()); } _ => {} - }; - - if self.tls.is_none() { - let tls = if tls { - Tls::Enabled(Default::default()) - } else { - Tls::Disabled - }; - - self.tls = Some(tls); } } - k @ "tlsinsecure" | k @ "tlsallowinvalidcertificates" => { - let val = get_bool!(value, k); - - let allow_invalid_certificates = if k == "tlsinsecure" { !val } else { val }; - - match self.tls { - Some(Tls::Disabled) => { - return Err(ErrorKind::InvalidArgument { - message: "'tlsInsecure' can't be set if tls=false".into(), + TLS_INSECURE => { + let val = get_bool!(value, key); + self.tls_insecure = Some(val); + + match self + .tls + .get_or_insert_with(|| Tls::Enabled(Default::default())) + { + Tls::Enabled(ref mut options) => { + options.allow_invalid_certificates = Some(val); + #[cfg(feature = "openssl-tls")] + { + options.allow_invalid_hostnames = Some(val); } - .into()) } - Some(Tls::Enabled(ref options)) - if options.allow_invalid_certificates.is_some() - && options.allow_invalid_certificates - != Some(allow_invalid_certificates) => - { - return Err(ErrorKind::InvalidArgument { - message: "all instances of 'tlsInsecure' and \ - 'tlsAllowInvalidCertificates' must be consistent (e.g. \ - 'tlsInsecure' cannot be true when \ - 'tlsAllowInvalidCertificates' is false, or vice-versa)" - .into(), - } - .into()); + Tls::Disabled => { + return Err(Error::invalid_argument(format!( + "cannot set {key} when TLS is disabled" + ))); } - Some(Tls::Enabled(ref mut options)) => { - options.allow_invalid_certificates = Some(allow_invalid_certificates); + } + } + TLS_ALLOW_INVALID_CERTIFICATES => { + let val = get_bool!(value, key); + + match self + .tls + .get_or_insert_with(|| Tls::Enabled(Default::default())) + { + Tls::Enabled(ref mut options) => { + options.allow_invalid_certificates = Some(val); } - None => { - self.tls = Some(Tls::Enabled( - TlsOptions::builder() - .allow_invalid_certificates(allow_invalid_certificates) - .build(), - )) + Tls::Disabled => { + return Err(Error::invalid_argument(format!( + "cannot set {key} when TLS is disabled" + ))) } } }
src/client/options/test.rs+53 −0 modified@@ -10,6 +10,7 @@ use crate::{ bson_util::get_int, client::options::{ClientOptions, ConnectionString, ServerAddress}, error::ErrorKind, + options::Tls, test::spec::deserialize_spec_tests, Client, }; @@ -428,3 +429,55 @@ async fn tls_cert_key_password_connect() { .await .unwrap(); } + +#[tokio::test] +async fn tls_insecure() { + let uri = "mongodb://localhost:27017?tls=true&tlsInsecure=true"; + let options = ClientOptions::parse(uri).await.unwrap(); + let Some(Tls::Enabled(tls_options)) = options.tls else { + panic!("expected tls options to be set"); + }; + assert_eq!(tls_options.allow_invalid_certificates, Some(true)); + #[cfg(feature = "openssl-tls")] + assert_eq!(tls_options.allow_invalid_hostnames, Some(true)); + + let uri = "mongodb://localhost:27017?tls=true&tlsInsecure=false"; + let options = ClientOptions::parse(uri).await.unwrap(); + let Some(Tls::Enabled(tls_options)) = options.tls else { + panic!("expected tls options to be set"); + }; + assert_eq!(tls_options.allow_invalid_certificates, Some(false)); + #[cfg(feature = "openssl-tls")] + assert_eq!(tls_options.allow_invalid_hostnames, Some(false)); + + let uri = "mongodb://localhost:27017?tls=false&tlsInsecure=true"; + let error = ClientOptions::parse(uri).await.unwrap_err(); + assert!(error.message().unwrap().contains("TLS is disabled")); + + let uri = "mongodb://localhost:27017?tlsInsecure=true&tls=false"; + let error = ClientOptions::parse(uri).await.unwrap_err(); + assert!(error + .message() + .unwrap() + .contains("other TLS options are set")); + + let uri = "mongodb://localhost:27017?tlsInsecure=true&tlsAllowInvalidCertificates=true"; + let error = ClientOptions::parse(uri).await.unwrap_err(); + assert!(error.message().unwrap().contains("cannot set both")); + + let uri = "mongodb://localhost:27017?tlsInsecure=true&tlsAllowInvalidCertificates=false"; + let error = ClientOptions::parse(uri).await.unwrap_err(); + assert!(error.message().unwrap().contains("cannot set both")); + + // TODO RUST-1896: uncomment these tests + // #[cfg(feature = "openssl-tls")] + // { + // let uri = "mongodb://localhost:27017?tlsInsecure=true&tlsAllowInvalidHostnames=true"; + // let error = ClientOptions::parse(uri).await.unwrap_err(); + // assert!(error.message().unwrap().contains("cannot set both")); + + // let uri = "mongodb://localhost:27017?tlsInsecure=true&tlsAllowInvalidHostnames=false"; + // let error = ClientOptions::parse(uri).await.unwrap_err(); + // assert!(error.message().unwrap().contains("cannot set both")); + // } +}
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-3p6w-gv5g-xjw9ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-11695ghsaADVISORY
- github.com/mongodb/mongo-rust-driver/commit/21ed6aeeea386628621b36a6af2a1a248cc87dcfghsaWEB
- github.com/mongodb/mongo-rust-driver/commit/b918cd6676331c45f26dd1acd13e230aaf17fe6dghsaWEB
- github.com/mongodb/mongo-rust-driver/pull/1453ghsaWEB
- jira.mongodb.org/browse/RUST-2264ghsaWEB
News mentions
0No linked articles in our index yet.