From 80e0f97511ef370bfd4dc8b4749087d43d3941dd Mon Sep 17 00:00:00 2001 From: mbaas038 Date: Fri, 13 Feb 2026 15:36:13 +0100 Subject: [PATCH 1/3] Add password masking in __repr__ --- CHANGES/1629.feature.rst | 2 ++ tests/test_url.py | 8 ++++---- yarl/_parse.py | 3 +++ yarl/_url.py | 18 ++++++++++++------ 4 files changed, 21 insertions(+), 10 deletions(-) create mode 100644 CHANGES/1629.feature.rst diff --git a/CHANGES/1629.feature.rst b/CHANGES/1629.feature.rst new file mode 100644 index 000000000..334b14856 --- /dev/null +++ b/CHANGES/1629.feature.rst @@ -0,0 +1,2 @@ +Mask the password component in ``yarl.URL.__repr__`` to prevent accidental credential exposure in logs and debug +output -- by :user:`mbaas038` and :user:`jhbuhrman`. diff --git a/tests/test_url.py b/tests/test_url.py index 37871fedb..34423c2d9 100644 --- a/tests/test_url.py +++ b/tests/test_url.py @@ -57,13 +57,13 @@ def test_url_is_not_str() -> None: def test_str() -> None: - url = URL("http://example.com:8888/path/to?a=1&b=2") - assert str(url) == "http://example.com:8888/path/to?a=1&b=2" + url = URL("http://user:password@example.com:8888/path/to?a=1&b=2") + assert str(url) == "http://user:password@example.com:8888/path/to?a=1&b=2" def test_repr() -> None: - url = URL("http://example.com") - assert "URL('http://example.com')" == repr(url) + url = URL("http://user:password@example.com") + assert "URL('http://user:********@example.com')" == repr(url) def test_origin() -> None: diff --git a/yarl/_parse.py b/yarl/_parse.py index bb64165c7..1e4a2fcd4 100644 --- a/yarl/_parse.py +++ b/yarl/_parse.py @@ -161,6 +161,7 @@ def make_netloc( host: str | None, port: int | None, encode: bool = False, + mask_password: bool = False, ) -> str: """Make netloc from parts. @@ -176,6 +177,8 @@ def make_netloc( if user is None and password is None: return ret if password is not None: + if mask_password: + password = "********" if not user: user = "" elif encode: diff --git a/yarl/_url.py b/yarl/_url.py index 75043d521..38f5ba06d 100644 --- a/yarl/_url.py +++ b/yarl/_url.py @@ -491,24 +491,30 @@ def build( def __init_subclass__(cls) -> NoReturn: raise TypeError(f"Inheriting a class {cls!r} from URL is forbidden") - def __str__(self) -> str: + def _make_string(self, mask_password: bool = False) -> str: if not self._path and self._netloc and (self._query or self._fragment): path = "/" else: path = self._path - if (port := self.explicit_port) is not None and port == DEFAULT_PORTS.get( - self._scheme - ): + if ( + (port := self.explicit_port) is not None + and port == DEFAULT_PORTS.get(self._scheme) + ) or mask_password: # port normalization - using None for default ports to remove from rendering # https://datatracker.ietf.org/doc/html/rfc3986.html#section-6.2.3 host = self.host_subcomponent - netloc = make_netloc(self.raw_user, self.raw_password, host, None) + netloc = make_netloc( + self.raw_user, self.raw_password, host, None, mask_password=True + ) else: netloc = self._netloc return unsplit_result(self._scheme, netloc, path, self._query, self._fragment) + def __str__(self) -> str: + return self._make_string(mask_password=False) + def __repr__(self) -> str: - return f"{self.__class__.__name__}('{str(self)}')" + return f"{self.__class__.__name__}('{self._make_string(mask_password=True)}')" def __bytes__(self) -> bytes: return str(self).encode("ascii") From 5e4f729d6d820401ab825243a4b77c07f1e93556 Mon Sep 17 00:00:00 2001 From: Martijn Baas <38461622+mbaas038@users.noreply.github.com> Date: Tue, 24 Feb 2026 17:37:22 +0100 Subject: [PATCH 2/3] Update yarl/_url.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix incorrect passing of mask_password parameter Co-authored-by: Jan-Hein Bührman --- yarl/_url.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarl/_url.py b/yarl/_url.py index 38f5ba06d..5359f2ebb 100644 --- a/yarl/_url.py +++ b/yarl/_url.py @@ -504,7 +504,7 @@ def _make_string(self, mask_password: bool = False) -> str: # https://datatracker.ietf.org/doc/html/rfc3986.html#section-6.2.3 host = self.host_subcomponent netloc = make_netloc( - self.raw_user, self.raw_password, host, None, mask_password=True + self.raw_user, self.raw_password, host, None, mask_password=mask_password ) else: netloc = self._netloc From 0ac2681395f59e57f5efb3ff60156791208e55c7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 24 Feb 2026 16:38:03 +0000 Subject: [PATCH 3/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- yarl/_url.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/yarl/_url.py b/yarl/_url.py index 5359f2ebb..ce9dcdf21 100644 --- a/yarl/_url.py +++ b/yarl/_url.py @@ -504,7 +504,11 @@ def _make_string(self, mask_password: bool = False) -> str: # https://datatracker.ietf.org/doc/html/rfc3986.html#section-6.2.3 host = self.host_subcomponent netloc = make_netloc( - self.raw_user, self.raw_password, host, None, mask_password=mask_password + self.raw_user, + self.raw_password, + host, + None, + mask_password=mask_password, ) else: netloc = self._netloc