From 74d03a281456c83e6053a980d5f67f9fbf6c9c01 Mon Sep 17 00:00:00 2001 From: Quratulain-bilal Date: Fri, 10 Jul 2026 23:45:44 +0500 Subject: [PATCH] fix(auth): return no matches, not raw ValueError, for a malformed URL (#3437) find_entries_for_url did (urlparse(url).hostname or "").lower() unguarded. a malformed authority (e.g. an unterminated ipv6 bracket "https://[::1") makes urlparse/hostname raise ValueError, so instead of the empty list the function already returns for a host-less url, a raw ValueError leaked out of the shared http client (build_request / open_url call this before any url validation). no auth entry can match such a url, so treat it like the host-less case and return no matches. added a regression test over an unterminated bracket and a bracketed non-ip host; confirmed it fails on the pre-fix code. --- src/specify_cli/authentication/config.py | 10 +++++++++- tests/test_authentication.py | 14 ++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/specify_cli/authentication/config.py b/src/specify_cli/authentication/config.py index 968cadc46..8d1faf80c 100644 --- a/src/specify_cli/authentication/config.py +++ b/src/specify_cli/authentication/config.py @@ -196,7 +196,15 @@ def find_entries_for_url( url: str, entries: list[AuthConfigEntry] ) -> list[AuthConfigEntry]: """Return entries whose ``hosts`` match the hostname of *url*.""" - hostname = (urlparse(url).hostname or "").lower() + # A malformed authority (e.g. an unterminated IPv6 bracket "https://[::1") + # makes urlparse/hostname raise ValueError. Treat that the same as a + # host-less URL: no entry can match, so return no matches rather than + # leaking a raw ValueError out of the shared HTTP client (build_request / + # open_url call this before any URL validation). + try: + hostname = (urlparse(url).hostname or "").lower() + except ValueError: + return [] if not hostname: return [] return [ diff --git a/tests/test_authentication.py b/tests/test_authentication.py index 917c698db..e8bc2b678 100644 --- a/tests/test_authentication.py +++ b/tests/test_authentication.py @@ -315,6 +315,20 @@ class TestFindEntriesForUrl: def test_empty_url_returns_empty(self): assert find_entries_for_url("", [_github_entry()]) == [] + @pytest.mark.parametrize( + "url", + [ + "https://[::1", # unterminated ipv6 bracket + "https://[not-an-ip]/file", # bracketed non-ip host + ], + ) + def test_malformed_url_returns_empty(self, url): + # A malformed authority makes urlparse/hostname raise ValueError. + # Since no entry can match such a URL, this must return no matches + # (like a host-less URL) rather than leaking a raw ValueError out of + # the shared HTTP client. + assert find_entries_for_url(url, [_github_entry()]) == [] + def test_empty_entries_returns_empty(self): assert find_entries_for_url("https://github.com/org/repo", []) == []