From 954b504ca4e2a81edd6558f3a19a1d343a717bd7 Mon Sep 17 00:00:00 2001 From: Rajeshwariba Vaghela Date: Sat, 24 Oct 2026 14:33:35 +0530 Subject: [PATCH 1/2] Mask repository credentials in repr Signed-off-by: Rajeshwariba Vaghela --- src/python_inspector/utils_pypi.py | 2 +- tests/test_utils.py | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/python_inspector/utils_pypi.py b/src/python_inspector/utils_pypi.py index 690aabec..28ba70d9 100644 --- a/src/python_inspector/utils_pypi.py +++ b/src/python_inspector/utils_pypi.py @@ -1502,7 +1502,7 @@ class PypiSimpleRepository: repr=False, ) - credentials = attr.ib(type=dict, default=None) + credentials = attr.ib(type=dict, default=None, repr=False) async def _get_package_versions_map( self, diff --git a/tests/test_utils.py b/tests/test_utils.py index d9c0fe5c..df33fa83 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -96,6 +96,19 @@ def test_get_netrc_auth_with_default(): ) +def test_repository_repr_does_not_expose_credentials(): + repo = PypiSimpleRepository( + index_url="https://example.com/simple/", + credentials={"login": "test-user", "password": "test-secret"}, + ) + + repo_repr = repr(repo) + + assert "test-user" not in repo_repr + assert "test-secret" not in repo_repr + assert "credentials" not in repo_repr + + @pytest.mark.asyncio @pytest.mark.skipif(sys.version_info < (3, 8), reason="requires python3.8 or higher") @mock.patch("python_inspector.utils_pypi.CACHE.get") From 24dba7223cd5774ddfcd0681991ce2278e5f66dc Mon Sep 17 00:00:00 2001 From: Rajeshwariba Vaghela Date: Sat, 24 Oct 2026 14:52:03 +0530 Subject: [PATCH 2/2] Mask repository passwords in repr Signed-off-by: Rajeshwariba Vaghela --- src/python_inspector/utils_pypi.py | 19 ++++++++++++++++++- tests/test_utils.py | 18 +++++++++++++----- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/python_inspector/utils_pypi.py b/src/python_inspector/utils_pypi.py index 28ba70d9..0f9f78a1 100644 --- a/src/python_inspector/utils_pypi.py +++ b/src/python_inspector/utils_pypi.py @@ -1459,6 +1459,23 @@ def tags(self): ################################################################################ +def _repr_credentials(credentials): + if credentials is None: + return repr(None) + + masked_credentials = credentials.copy() + password = masked_credentials.get("password") + + if password is not None: + masked_credentials["password"] = ( + "***" + if len(password) < 12 + else f"{password[:3]}***{password[-3:]}" + ) + + return repr(masked_credentials) + + @attr.attributes class PypiSimpleRepository: """ @@ -1502,7 +1519,7 @@ class PypiSimpleRepository: repr=False, ) - credentials = attr.ib(type=dict, default=None, repr=False) + credentials = attr.ib(type=dict, default=None, repr=_repr_credentials) async def _get_package_versions_map( self, diff --git a/tests/test_utils.py b/tests/test_utils.py index df33fa83..06cf2410 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -96,17 +96,25 @@ def test_get_netrc_auth_with_default(): ) -def test_repository_repr_does_not_expose_credentials(): +@pytest.mark.parametrize( + ("password", "expected"), + [ + ("short123", "***"), + ("abcdefghijkl", "abc***jkl"), + ("my-very-secret-password", "my-***ord"), + ], +) +def test_repository_repr_masks_password(password, expected): repo = PypiSimpleRepository( index_url="https://example.com/simple/", - credentials={"login": "test-user", "password": "test-secret"}, + credentials={"login": "test-user", "password": password}, ) repo_repr = repr(repo) - assert "test-user" not in repo_repr - assert "test-secret" not in repo_repr - assert "credentials" not in repo_repr + assert "test-user" in repo_repr + assert expected in repo_repr + assert password not in repo_repr @pytest.mark.asyncio