Skip to content

Mask password in URL.__repr__ for safer debugging output #1629#1630

Open
mbaas038 wants to merge 3 commits intoaio-libs:masterfrom
mbaas038:feature/add-password-masking-in-repr-1629
Open

Mask password in URL.__repr__ for safer debugging output #1629#1630
mbaas038 wants to merge 3 commits intoaio-libs:masterfrom
mbaas038:feature/add-password-masking-in-repr-1629

Conversation

@mbaas038
Copy link
Copy Markdown

What do these changes do?

  • yarl.URL.__repr__ exposed passwords in URLs, risking credential leakage in logs, tracebacks, or debugging output.
  • The password component is masked in __repr__ while leaving other URL parts unchanged.
  • Added conditional masking logic to __repr__ that replaces the password with a placeholder (********) if present.
  • Improves security by preventing accidental exposure.
  • Does not affect URL functionality or internal storage.

Are there changes in behavior for the user?

  • Slight change in repr() output — may affect tests relying on exact string output.

Related issue number

Checklist

  • I think the code is well written
  • Unit tests for the changes exist
  • Documentation reflects the changes

This PR was kindly supported by Sopra Steria Pythoneers

@psf-chronographer psf-chronographer bot added the bot:chronographer:provided There is a change note present in this PR label Feb 13, 2026
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq bot commented Feb 13, 2026

Merging this PR will degrade performance by 15.36%

❌ 3 regressed benchmarks
✅ 96 untouched benchmarks

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
test_url_with_path_to_string 217.6 µs 255.6 µs -14.87%
test_url_with_query_to_string 254.3 µs 291.6 µs -12.78%
test_url_to_string 203.8 µs 240.8 µs -15.36%

Comparing mbaas038:feature/add-password-masking-in-repr-1629 (0ac2681) with master (7829b01)

Open in CodSpeed

@codecov
Copy link
Copy Markdown

codecov bot commented Feb 13, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.37%. Comparing base (7829b01) to head (0ac2681).

❌ Your project check has failed because the head coverage (97.63%) is below the target coverage (100.00%). You can increase the head coverage or adjust the target coverage.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #1630   +/-   ##
=======================================
  Coverage   99.37%   99.37%           
=======================================
  Files          30       30           
  Lines        5940     5952   +12     
  Branches      282      283    +1     
=======================================
+ Hits         5903     5915   +12     
  Misses         22       22           
  Partials       15       15           
Flag Coverage Δ
CI-GHA 99.37% <100.00%> (+<0.01%) ⬆️
MyPy 97.63% <100.00%> (+<0.01%) ⬆️
OS-Linux 99.73% <100.00%> (+<0.01%) ⬆️
OS-Windows 98.66% <100.00%> (+<0.01%) ⬆️
OS-macOS 98.80% <100.00%> (+<0.01%) ⬆️
Py-3.10.11 98.63% <100.00%> (+<0.01%) ⬆️
Py-3.10.19 99.65% <100.00%> (+<0.01%) ⬆️
Py-3.11.14 99.65% <100.00%> (+<0.01%) ⬆️
Py-3.11.9 98.63% <100.00%> (+<0.01%) ⬆️
Py-3.12.10 98.63% <100.00%> (+<0.01%) ⬆️
Py-3.12.12 99.65% <100.00%> (+<0.01%) ⬆️
Py-3.13.11 99.68% <100.00%> (-0.03%) ⬇️
Py-3.13.11t ?
Py-3.13.12 98.78% <100.00%> (-0.93%) ⬇️
Py-3.13.12t 99.70% <100.00%> (+<0.01%) ⬆️
Py-3.14.2 99.68% <100.00%> (-0.03%) ⬇️
Py-3.14.2t ?
Py-3.14.3 98.79% <100.00%> (-0.92%) ⬇️
Py-3.14.3t 99.70% <100.00%> (+<0.01%) ⬆️
Py-pypy3.10.16-7.3.19 99.31% <100.00%> (+<0.01%) ⬆️
VM-macos-latest 98.80% <100.00%> (+<0.01%) ⬆️
VM-ubuntu-latest 99.73% <100.00%> (+<0.01%) ⬆️
VM-windows-latest 98.66% <100.00%> (+<0.01%) ⬆️
pytest 99.75% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread yarl/_url.py
@mbaas038 mbaas038 force-pushed the feature/add-password-masking-in-repr-1629 branch from 2f914e6 to 80e0f97 Compare February 16, 2026 08:27
Comment thread yarl/_url.py Outdated
mbaas038 and others added 2 commits February 24, 2026 17:37
Fix incorrect passing of mask_password parameter

Co-authored-by: Jan-Hein Bührman <buhrman@xs4all.nl>
Comment thread yarl/_url.py

def __repr__(self) -> str:
return f"{self.__class__.__name__}('{str(self)}')"
return f"{self.__class__.__name__}('{self._make_string(mask_password=True)}')"
Copy link
Copy Markdown

@jhbuhrman jhbuhrman Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An additional consideration: since repr() returns now a string that wouldn't yield anymore an object with the same value when passed to eval() in case .password is not None, it could perhaps be better to change the repr (perhaps in that case only, .password is not None) into a string enclosed in angle brackets that contains the name of the type of the object together with additional information often including the name and address of the object. [Italic parts quoted from http://docs.python.org/3/library/functions.html#repr ].

Suggestion:

Suggested change
return f"{self.__class__.__name__}('{self._make_string(mask_password=True)}')"
return (
f"{self.__class__.__name__}(repr({self._make_string(mask_password=False)}))"
if self.password is None
else f"<{self.__class__.__name__} repr({self._make_string(mask_password=True)}) (masked)>"
)

This also requires changing the tests.

A more general remark: the explicit insertion of single quotes is tricky anyway for certain edge cases, better rely on repr() on the generated string value.

naarob pushed a commit to naarob/yarl that referenced this pull request Mar 26, 2026
…ry (aio-libs#1643), split_url delim perf

fix aio-libs#1630: URL.__repr__ exposed passwords in plaintext, risking credential
  leakage in logs and tracebacks. Added password masking with '********'.
  str(), == and all other operations remain unaffected.

fix aio-libs#1643: uuid.UUID was converted to int in query params because UUID
  implements __int__(), matching the SupportsInt protocol. Fixed by checking
  whether type(v).__str__ is not object.__str__ before using int() conversion.
  This is a general solution that works for any type with a meaningful __str__.

perf: split_url() delimiter search refactored from a for-loop over delim_chars
  to 3 conditional find() calls using the already-computed has_hash and
  has_question_mark flags. Reduces per-call overhead for URL-heavy workloads.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot:chronographer:provided There is a change note present in this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Mask password in URL.__repr__ for safer debugging output

2 participants