diff --git a/news/14232.bugfix.rst b/news/14232.bugfix.rst new file mode 100644 index 0000000000..aab7c8a6a8 --- /dev/null +++ b/news/14232.bugfix.rst @@ -0,0 +1 @@ +Prevent wheel cache origin mismatch warnings from exposing URL credentials. diff --git a/src/pip/_internal/cache.py b/src/pip/_internal/cache.py index 462231a36e..0a69a45240 100644 --- a/src/pip/_internal/cache.py +++ b/src/pip/_internal/cache.py @@ -269,6 +269,7 @@ def get_cache_entry( @staticmethod def record_download_origin(cache_dir: str, download_info: DirectUrl) -> None: origin_path = Path(cache_dir) / ORIGIN_JSON_NAME + download_url = download_info.to_dict_compat()["url"] if origin_path.exists(): try: origin = DirectUrl.from_json(origin_path.read_text(encoding="utf-8")) @@ -282,13 +283,14 @@ def record_download_origin(cache_dir: str, download_info: DirectUrl) -> None: else: # TODO: use DirectUrl.equivalent when # https://github.com/pypa/pip/pull/10564 is merged. - if origin.url != download_info.url: + origin_url = origin.to_dict_compat()["url"] + if origin_url != download_url: logger.warning( "Origin URL %s in cache entry %s does not match download URL " "%s. This is likely a pip bug or a cache corruption issue. " "Will overwrite it with the new value.", - origin.url, + origin_url, cache_dir, - download_info.url, + download_url, ) origin_path.write_text(download_info.to_json(), encoding="utf-8") diff --git a/tests/unit/test_cache.py b/tests/unit/test_cache.py index 2458fc798a..f9f3d284dd 100644 --- a/tests/unit/test_cache.py +++ b/tests/unit/test_cache.py @@ -7,6 +7,7 @@ from pip._vendor.packaging.tags import Tag, interpreter_name, interpreter_version from pip._internal.cache import SimpleWheelCache, WheelCache, _hash_dict +from pip._internal.models.direct_url import ArchiveInfo, DirectUrl from pip._internal.models.link import Link from pip._internal.utils.misc import ensure_dir from pip._internal.utils.urls import path_to_url @@ -152,3 +153,40 @@ def test_wheel_cache_entry_none_for_existing_directory(tmpdir: Path) -> None: assert wc.get_cache_entry(link, "example", supported_tags) is None assert wc.get(link, "example", supported_tags) is link + + +def test_record_download_origin_ignores_stripped_auth_difference( + tmp_path: Path, caplog: pytest.LogCaptureFixture +) -> None: + download_info = DirectUrl( + url="https://user:secret@example.com/pkg.tar.gz", + archive_info=ArchiveInfo(), + ) + + WheelCache.record_download_origin(str(tmp_path), download_info) + WheelCache.record_download_origin(str(tmp_path), download_info) + + assert caplog.records == [] + + +def test_record_download_origin_redacts_auth_from_warning( + tmp_path: Path, caplog: pytest.LogCaptureFixture +) -> None: + old_info = DirectUrl( + url="https://old-user:old-secret@old.example/pkg.tar.gz", + archive_info=ArchiveInfo(), + ) + new_info = DirectUrl( + url="https://new-user:new-secret@new.example/pkg.tar.gz", + archive_info=ArchiveInfo(), + ) + WheelCache.record_download_origin(str(tmp_path), old_info) + + WheelCache.record_download_origin(str(tmp_path), new_info) + + assert len(caplog.records) == 1 + message = caplog.records[0].getMessage() + assert "old-secret" not in message + assert "new-secret" not in message + assert "https://old.example/pkg.tar.gz" in message + assert "https://new.example/pkg.tar.gz" in message