diff --git a/crates/distribution-types/src/index_url.rs b/crates/distribution-types/src/index_url.rs index 1dc4cc332..3467ccf41 100644 --- a/crates/distribution-types/src/index_url.rs +++ b/crates/distribution-types/src/index_url.rs @@ -51,6 +51,19 @@ impl IndexUrl { Self::Path(url) => url.raw(), } } + + /// Return the redacted URL for the index, omitting any sensitive credentials. + pub fn redacted(&self) -> Cow<'_, Url> { + let url = self.url(); + if url.username().is_empty() && url.password().is_none() { + Cow::Borrowed(url) + } else { + let mut url = url.clone(); + let _ = url.set_username(""); + let _ = url.set_password(None); + Cow::Owned(url) + } + } } impl Display for IndexUrl { diff --git a/crates/uv-resolver/src/resolution.rs b/crates/uv-resolver/src/resolution.rs index c6fe8cfe9..535417a85 100644 --- a/crates/uv-resolver/src/resolution.rs +++ b/crates/uv-resolver/src/resolution.rs @@ -746,7 +746,8 @@ impl std::fmt::Display for DisplayResolutionGraph<'_> { // `# from https://pypi.org/simple`). if self.include_index_annotation { if let Some(index) = node.index() { - writeln!(f, "{}", format!(" # from {index}").green())?; + let url = index.redacted(); + writeln!(f, "{}", format!(" # from {url}").green())?; } } } diff --git a/crates/uv/tests/pip_compile.rs b/crates/uv/tests/pip_compile.rs index e89ad8ec0..82360443a 100644 --- a/crates/uv/tests/pip_compile.rs +++ b/crates/uv/tests/pip_compile.rs @@ -7640,6 +7640,47 @@ fn compile_index_url_fallback_prefer_primary() -> Result<()> { Ok(()) } +/// Ensure that the username and the password are omitted when +/// index annotations are displayed via `--emit-index-annotation`. +#[test] +fn emit_index_annotation_hide_password() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("requests")?; + + uv_snapshot!(context.compile() + .arg("requirements.in") + .arg("--emit-index-annotation") + .env("UV_INDEX_URL", "https://test-user:test-password@pypi.org/simple"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in --emit-index-annotation + certifi==2024.2.2 + # via requests + # from https://pypi.org/simple + charset-normalizer==3.3.2 + # via requests + # from https://pypi.org/simple + idna==3.6 + # via requests + # from https://pypi.org/simple + requests==2.31.0 + # from https://pypi.org/simple + urllib3==2.2.1 + # via requests + # from https://pypi.org/simple + + ----- stderr ----- + Resolved 5 packages in [TIME] + "### + ); + + Ok(()) +} + /// Ensure that `--emit-index-annotation` prints the index URL for each package. #[test] fn emit_index_annotation_pypi_org_simple() -> Result<()> {