fix(util): redact URL credentials without corrupting the host - #2270
Merged
Byron merged 2 commits intoOct 5, 2026
Merged
Conversation
remove_password_if_present() redacted credentials by substituting them
into url.netloc with str.replace(), which rewrites every occurrence
anywhere in the netloc -- including the host.
https://git@github.com/user/repo.git
-> https://*****@*****hub.com/user/repo.git
"git" is the most common Git username and a substring of "github.com",
so this hits the common case. An empty username or password makes it
worse: str.replace("", "*****") matches at every position, so
https://:token@fakerepo.example.com/testrepo
comes back shredded into a URL roughly ten times longer, which is what
lands in GitCommandError messages when a fetch fails.
Rebuild the netloc from its userinfo instead, so only the credentials
are replaced. The existing test already covered the empty-password case
but only asserted the password is absent, which any mangling satisfies.
The function is the single redaction path used for logged command lines
and for exception messages (git/cmd.py, git/exc.py, git/repo/base.py),
so this fixes all of them at once.
Member
|
Thanks a lot, good catch! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
remove_password_if_present()redacts credentials by substituting the username/password intourl.netlocwithstr.replace().str.replace()rewrites every occurrence anywhere in the string, and the netloc contains the host and port as well as the userinfo — so the host gets corrupted too.The username
gitis the most common Git username and is a substring ofgithub.com:An empty username or password makes it considerably worse:
urlsplitreturns''(notNone) forhttps://:token@host, andstr.replace('', '*****')matches at every position:That second case is the reason this is more than cosmetic: it inflates the message ~10x and makes the remote unidentifiable, at exactly the moment a user is reading a
GitCommandErrorto work out which remote failed.This function is the single redaction path used for logged command lines and for exception messages (
git/cmd.py,git/exc.py,git/repo/base.py), so the fix is one guard in the shared function rather than a patch per caller.The fix
Rebuild the netloc from its userinfo rather than substituting into it:
urlsplit()already returnsusername/passwordas non-Noneonly when thenetloc carries an
@, so by this point in the loop a userinfo boundary isguaranteed and the
rpartitionseparator never needs a separate guard.Output is byte-identical to the current behaviour for every non-overlapping input, including all three inputs in the existing test and port/case preservation.
Why the existing test did not catch it
test_remove_password_from_command_linealready contains the empty-password case ascmd_3, and it passes today — because it only assertspassword not in " ".join(...), which any mangling satisfies. The two new tests assert the full redacted command line, which is what pins the host in place.Testing
Both new tests fail on
mainand pass with this change. The pre-existingtest_remove_password_from_command_linepasses both before and after, so there is no behaviour change for the cases it already covers.ruff checkandruff format --checkare clean.The suite was not run locally (this was prepared on a memory-constrained machine where
pip installof the test dependencies is not possible), so the new tests have not been executed against the real package — CI is the verification. What was run locally isremove_password_if_present's body copied verbatim out of this branch with the assertion table below, executed as a standalone script under the stdlib only:ddtcases intest_remove_password_preserves_url_componentshttps://git@github.com/user/repo.gitandssh://git@github.com/u/r.gitcaseshttp.*.extraheader=Authorization:input, confirming the header branch is untouchedAll pass, and the pre-fix code reproduces both reported outputs (
https://*****@*****hub.com/...and the shredded host) exactly as described above.