diff --git a/botocore/httpsession.py b/botocore/httpsession.py index e242c44e9b..1701d3579c 100644 --- a/botocore/httpsession.py +++ b/botocore/httpsession.py @@ -210,13 +210,17 @@ def mask_proxy_url(proxy_url): :return: Masked proxy url, i.e. https://***:***@proxy.com """ - mask = '*' * 3 parsed_url = urlparse(proxy_url) - if parsed_url.username: - proxy_url = proxy_url.replace(parsed_url.username, mask, 1) - if parsed_url.password: - proxy_url = proxy_url.replace(parsed_url.password, mask, 1) - return proxy_url + if not parsed_url.username: + return proxy_url + mask = '*' * 3 + # Rebuild the netloc from the parsed userinfo/host so only the credential + # fields are masked. A substring replace can spend the mask on an earlier + # occurrence of the credential value (e.g. in the scheme or host) and leave + # the real secret in place. + _, _, host = parsed_url.netloc.rpartition('@') + userinfo = f'{mask}:{mask}' if parsed_url.password else mask + return parsed_url._replace(netloc=f'{userinfo}@{host}').geturl() def _is_ipaddress(host): diff --git a/tests/unit/test_http_session.py b/tests/unit/test_http_session.py index 5e6a34142e..edb797fca4 100644 --- a/tests/unit/test_http_session.py +++ b/tests/unit/test_http_session.py @@ -95,6 +95,13 @@ def test_get_cert_path_certifi_or_default(self): ('http://user:pass@192.168.1.1', 'http://***:***@192.168.1.1'), ('http://user:pass@[::1]', 'http://***:***@[::1]'), ('http://user:pass@[::1]:80', 'http://***:***@[::1]:80'), + # credential value also appears earlier in the url + ( + 'https://user:https@proxy.example.com', + 'https://***:***@proxy.example.com', + ), + ('http://ttp:secret@host.com', 'http://***:***@host.com'), + ('http://myproxy.amazonaws.com', 'http://myproxy.amazonaws.com'), ), ) def test_mask_proxy_url(proxy_url, expected_mask_url):