From 03d150cd313a4967ae37b5fc37078da0665fcf1a Mon Sep 17 00:00:00 2001 From: snoopuppy582 Date: Thu, 14 May 2026 02:53:17 +0900 Subject: [PATCH] fix(helpers): mask credentials in git URL debug logs --- src/semantic_release/helpers.py | 14 ++++++++++++-- tests/unit/semantic_release/test_helpers.py | 20 ++++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/semantic_release/helpers.py b/src/semantic_release/helpers.py index c50369575..8ec505a16 100644 --- a/src/semantic_release/helpers.py +++ b/src/semantic_release/helpers.py @@ -9,7 +9,7 @@ from pathlib import Path, PurePosixPath from re import IGNORECASE, compile as regexp from typing import TYPE_CHECKING, Any, Callable, NamedTuple, Sequence, TypeVar -from urllib.parse import urlsplit +from urllib.parse import urlsplit, urlunsplit from semantic_release.globals import logger @@ -215,6 +215,16 @@ class ParsedGitUrl(NamedTuple): repo_name: str +def _hide_credentials_in_url(url: str) -> str: + url_parts = urlsplit(url) + + if not url_parts.scheme or "@" not in url_parts.netloc: + return url + + _, _, host = url_parts.netloc.rpartition("@") + return urlunsplit(url_parts._replace(netloc=f"@{host}")) + + @lru_cache(maxsize=512) def parse_git_url(url: str) -> ParsedGitUrl: """ @@ -242,7 +252,7 @@ def parse_git_url(url: str) -> ParsedGitUrl: Raises ValueError if the url can't be parsed. """ - logger.debug("Parsing git url %r", url) + logger.debug("Parsing git url %r", _hide_credentials_in_url(url)) # Normalizers are a list of tuples of (pattern, replacement) normalizers = [ diff --git a/tests/unit/semantic_release/test_helpers.py b/tests/unit/semantic_release/test_helpers.py index 4877d3892..840363604 100644 --- a/tests/unit/semantic_release/test_helpers.py +++ b/tests/unit/semantic_release/test_helpers.py @@ -2,6 +2,7 @@ import pytest +from semantic_release.globals import logger from semantic_release.helpers import ParsedGitUrl, parse_git_url, sort_numerically @@ -120,6 +121,25 @@ def test_parse_valid_git_urls(url: str, expected: ParsedGitUrl): assert expected == parse_git_url(url) +def test_parse_git_url_does_not_log_credentials(caplog: pytest.LogCaptureFixture): + """Test that credentials in git urls are masked before logging.""" + secret = "ghp_secret_token" + url = f"https://x-oauth-basic:{secret}@github.example.com/owner/project.git" + + parse_git_url.cache_clear() + caplog.set_level("DEBUG", logger=logger.name) + + assert ParsedGitUrl( + "https", + f"x-oauth-basic:{secret}@github.example.com", + "owner", + "project", + ) == parse_git_url(url) + assert secret not in caplog.text + assert "x-oauth-basic" not in caplog.text + assert "@github.example.com" in caplog.text + + @pytest.mark.parametrize( "url", [