diff --git a/src/python_inspector/utils_pypi.py b/src/python_inspector/utils_pypi.py index 690aabec..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) + 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 d9c0fe5c..06cf2410 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -96,6 +96,27 @@ def test_get_netrc_auth_with_default(): ) +@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": password}, + ) + + repo_repr = repr(repo) + + assert "test-user" in repo_repr + assert expected in repo_repr + assert password 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")