From d89fa76b18338427bbcb03783367b38dfa8152a1 Mon Sep 17 00:00:00 2001 From: Tyler Earls Date: Tue, 18 Nov 2025 11:56:15 -0600 Subject: [PATCH 1/3] add fix to filter out empty SSL_CERT_FILE with unit test to support it --- crates/uv-client/tests/it/ssl_certs.rs | 57 ++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/crates/uv-client/tests/it/ssl_certs.rs b/crates/uv-client/tests/it/ssl_certs.rs index 6c88cd7d5de3c..61c575c6e2fa7 100644 --- a/crates/uv-client/tests/it/ssl_certs.rs +++ b/crates/uv-client/tests/it/ssl_certs.rs @@ -157,6 +157,63 @@ async fn ssl_env_vars() -> Result<()> { std::env::remove_var(EnvVars::SSL_CERT_FILE); } + // ** Set SSL_CERT_FILE to an empty string + // ** Then verify it is treated as unset (falls back to default behavior) + + unsafe { + std::env::set_var(EnvVars::SSL_CERT_FILE, ""); + } + let (server_task, addr) = start_https_user_agent_server(&standalone_server_cert).await?; + let url = DisplaySafeUrl::from_str(&format!("https://{addr}"))?; + let cache = Cache::temp()?.init()?; + let client = RegistryClientBuilder::new(BaseClientBuilder::default(), cache).build(); + let res = client + .cached_client() + .uncached() + .for_host(&url) + .get(Url::from(url)) + .send() + .await; + unsafe { + std::env::remove_var(EnvVars::SSL_CERT_FILE); + } + + // Empty SSL_CERT_FILE should be ignored, falling back to default certificate handling. + // The connection will fail because we're using a self-signed cert without providing the cert, + // but the important thing is it doesn't fail due to "empty path does not exist". + let Some(reqwest_middleware::Error::Middleware(middleware_error)) = res.err() else { + panic!("expected middleware error"); + }; + let reqwest_error = middleware_error + .chain() + .find_map(|err| { + err.downcast_ref::().map(|err| { + if let reqwest_middleware::Error::Reqwest(inner) = err { + inner + } else { + panic!("expected reqwest error") + } + }) + }) + .expect("expected reqwest error"); + assert!(reqwest_error.is_connect()); + + // Validate the server error - should be UnknownCA (not a path-related error) + let server_res = server_task.await?; + let expected_err = if let Err(anyhow_err) = server_res + && let Some(io_err) = anyhow_err.downcast_ref::() + && let Some(wrapped_err) = io_err.get_ref() + && let Some(tls_err) = wrapped_err.downcast_ref::() + && matches!( + tls_err, + rustls::Error::AlertReceived(AlertDescription::UnknownCA) + ) { + true + } else { + false + }; + assert!(expected_err); + // ** Set SSL_CERT_DIR to our cert dir as well as some other dir that does not exist // ** Then verify our request still successfully establishes a connection From bec7d895fa80a41e26b9c3af1eb6be80a7898b5a Mon Sep 17 00:00:00 2001 From: Tyler Earls Date: Thu, 20 Nov 2025 12:31:50 -0600 Subject: [PATCH 2/3] remove test case with false positive --- crates/uv-client/tests/it/ssl_certs.rs | 57 -------------------------- 1 file changed, 57 deletions(-) diff --git a/crates/uv-client/tests/it/ssl_certs.rs b/crates/uv-client/tests/it/ssl_certs.rs index 61c575c6e2fa7..6c88cd7d5de3c 100644 --- a/crates/uv-client/tests/it/ssl_certs.rs +++ b/crates/uv-client/tests/it/ssl_certs.rs @@ -157,63 +157,6 @@ async fn ssl_env_vars() -> Result<()> { std::env::remove_var(EnvVars::SSL_CERT_FILE); } - // ** Set SSL_CERT_FILE to an empty string - // ** Then verify it is treated as unset (falls back to default behavior) - - unsafe { - std::env::set_var(EnvVars::SSL_CERT_FILE, ""); - } - let (server_task, addr) = start_https_user_agent_server(&standalone_server_cert).await?; - let url = DisplaySafeUrl::from_str(&format!("https://{addr}"))?; - let cache = Cache::temp()?.init()?; - let client = RegistryClientBuilder::new(BaseClientBuilder::default(), cache).build(); - let res = client - .cached_client() - .uncached() - .for_host(&url) - .get(Url::from(url)) - .send() - .await; - unsafe { - std::env::remove_var(EnvVars::SSL_CERT_FILE); - } - - // Empty SSL_CERT_FILE should be ignored, falling back to default certificate handling. - // The connection will fail because we're using a self-signed cert without providing the cert, - // but the important thing is it doesn't fail due to "empty path does not exist". - let Some(reqwest_middleware::Error::Middleware(middleware_error)) = res.err() else { - panic!("expected middleware error"); - }; - let reqwest_error = middleware_error - .chain() - .find_map(|err| { - err.downcast_ref::().map(|err| { - if let reqwest_middleware::Error::Reqwest(inner) = err { - inner - } else { - panic!("expected reqwest error") - } - }) - }) - .expect("expected reqwest error"); - assert!(reqwest_error.is_connect()); - - // Validate the server error - should be UnknownCA (not a path-related error) - let server_res = server_task.await?; - let expected_err = if let Err(anyhow_err) = server_res - && let Some(io_err) = anyhow_err.downcast_ref::() - && let Some(wrapped_err) = io_err.get_ref() - && let Some(tls_err) = wrapped_err.downcast_ref::() - && matches!( - tls_err, - rustls::Error::AlertReceived(AlertDescription::UnknownCA) - ) { - true - } else { - false - }; - assert!(expected_err); - // ** Set SSL_CERT_DIR to our cert dir as well as some other dir that does not exist // ** Then verify our request still successfully establishes a connection From d0f4a7248adb3924d79183a21081630206fa3271 Mon Sep 17 00:00:00 2001 From: Tyler Earls Date: Sun, 25 Jan 2026 11:37:16 -0600 Subject: [PATCH 3/3] re-add is_empty() check --- crates/uv-client/src/base_client.rs | 54 +++++++++++++++-------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/crates/uv-client/src/base_client.rs b/crates/uv-client/src/base_client.rs index f120917f2ac82..01f2a20c2d9ef 100644 --- a/crates/uv-client/src/base_client.rs +++ b/crates/uv-client/src/base_client.rs @@ -420,33 +420,35 @@ impl<'a> BaseClientBuilder<'a> { // Checks for the presence of `SSL_CERT_FILE`. // Certificate loading support is delegated to `rustls-native-certs`. // See https://github.com/rustls/rustls-native-certs/blob/813790a297ad4399efe70a8e5264ca1b420acbec/src/lib.rs#L118-L125 - let ssl_cert_file_exists = env::var_os(EnvVars::SSL_CERT_FILE).is_some_and(|path| { - let path = Path::new(&path); - match path.metadata() { - Ok(metadata) if metadata.is_file() => true, - Ok(_) => { - warn_user_once!( - "Ignoring invalid `SSL_CERT_FILE`. Path is not a file: {}.", - path.simplified_display().cyan() - ); - false - } - Err(err) if err.kind() == std::io::ErrorKind::NotFound => { - warn_user_once!( - "Ignoring invalid `SSL_CERT_FILE`. Path does not exist: {}.", - path.simplified_display().cyan() - ); - false - } - Err(err) => { - warn_user_once!( - "Ignoring invalid `SSL_CERT_FILE`. Path is not accessible: {} ({err}).", - path.simplified_display().cyan() - ); - false + let ssl_cert_file_exists = env::var_os(EnvVars::SSL_CERT_FILE) + .filter(|v| !v.is_empty()) + .is_some_and(|path| { + let path = Path::new(&path); + match path.metadata() { + Ok(metadata) if metadata.is_file() => true, + Ok(_) => { + warn_user_once!( + "Ignoring invalid `SSL_CERT_FILE`. Path is not a file: {}.", + path.simplified_display().cyan() + ); + false + } + Err(err) if err.kind() == std::io::ErrorKind::NotFound => { + warn_user_once!( + "Ignoring invalid `SSL_CERT_FILE`. Path does not exist: {}.", + path.simplified_display().cyan() + ); + false + } + Err(err) => { + warn_user_once!( + "Ignoring invalid `SSL_CERT_FILE`. Path is not accessible: {} ({err}).", + path.simplified_display().cyan() + ); + false + } } - } - }); + }); // Checks for the presence of `SSL_CERT_DIR`. // Certificate loading support is delegated to `rustls-native-certs`.