Fix gitcookies domain matching to correctly handle wildcard entries

Authentication silently failed for some hosts because the gitcookies
domain match was both too narrow and too broad. The original substring
check (`domain in line`) matched the domain string anywhere in the
cookie record, including the value field, while entries stored with the
standard wildcard prefix (a leading dot, e.g. `.gerrit.example.com`)
were skipped entirely.

Parse the cookie record first and match the extracted domain field
according to the gitcookies wildcard convention: an entry with a leading
dot matches both the bare domain and any subdomain, while an entry
without one requires an exact host match. This avoids the opposite error
of letting a non-wildcard entry such as `my-gerrit.com` match an
unrelated `sub.my-gerrit.com`.

Add unit tests covering wildcard subdomain and exact matches and
confirming non-wildcard entries do not match subdomains.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Change-Id: I746d2ba13e74071e5a8cc9119fa5ef8b6ae63d76
diff --git a/gerrit_mcp_server/gerrit_auth.py b/gerrit_mcp_server/gerrit_auth.py
index 614e3bb..12b86d5 100644
--- a/gerrit_mcp_server/gerrit_auth.py
+++ b/gerrit_mcp_server/gerrit_auth.py
@@ -57,9 +57,20 @@
         )
         with open(gitcookies_path, "r") as f:
             for line in f:
-                if domain in line:
-                    parts = line.strip().split("\t")
-                    if len(parts) == 7:
+                parts = line.strip().split("\t")
+                if len(parts) == 7:
+                    cookie_domain = parts[0]
+                    # Support exact match and wildcard (leading dot) domains.
+                    # A leading dot means the entry also matches subdomains;
+                    # without it, only an exact host match is allowed.
+                    if cookie_domain.startswith("."):
+                        normalized = cookie_domain.lstrip(".")
+                        match = domain == normalized or domain.endswith(
+                            "." + normalized
+                        )
+                    else:
+                        match = domain == cookie_domain
+                    if match:
                         last_found_cookie = f"{parts[5]}={parts[6]}"
 
         if last_found_cookie:
diff --git a/tests/unit/test_gerrit_auth.py b/tests/unit/test_gerrit_auth.py
index 3838a31..812bf2f 100644
--- a/tests/unit/test_gerrit_auth.py
+++ b/tests/unit/test_gerrit_auth.py
@@ -85,6 +85,43 @@
             command = gerrit_auth._get_auth_for_gitcookies(url, config)
         self.assertEqual(command, ["curl", "-b", "o=git-lasttoken", "-L"])
 
+    @patch("os.path.exists", return_value=True)
+    def test_get_auth_for_gitcookies_wildcard_matches_subdomain(self, mock_exists):
+        """A leading-dot (wildcard) entry matches a subdomain of that domain."""
+        config = {"gitcookies_path": "~/.gitcookies"}
+        url = "https://sub.my-gerrit.com"
+        m = mock_open(
+            read_data=".my-gerrit.com\tTRUE\t/\tTRUE\t2147483647\to\tgit-token"
+        )
+        with patch("builtins.open", m):
+            command = gerrit_auth._get_auth_for_gitcookies(url, config)
+        self.assertEqual(command, ["curl", "-b", "o=git-token", "-L"])
+
+    @patch("os.path.exists", return_value=True)
+    def test_get_auth_for_gitcookies_wildcard_matches_exact(self, mock_exists):
+        """A leading-dot (wildcard) entry also matches the bare domain."""
+        config = {"gitcookies_path": "~/.gitcookies"}
+        url = "https://my-gerrit.com"
+        m = mock_open(
+            read_data=".my-gerrit.com\tTRUE\t/\tTRUE\t2147483647\to\tgit-token"
+        )
+        with patch("builtins.open", m):
+            command = gerrit_auth._get_auth_for_gitcookies(url, config)
+        self.assertEqual(command, ["curl", "-b", "o=git-token", "-L"])
+
+    @patch("os.path.exists", return_value=True)
+    def test_get_auth_for_gitcookies_non_wildcard_ignores_subdomain(self, mock_exists):
+        """A non-wildcard entry must NOT match a subdomain of that domain."""
+        config = {"gitcookies_path": "~/.gitcookies"}
+        url = "https://sub.my-gerrit.com"
+        m = mock_open(
+            read_data="my-gerrit.com\tFALSE\t/\tTRUE\t2147483647\to\tgit-token"
+        )
+        with patch("builtins.open", m):
+            command = gerrit_auth._get_auth_for_gitcookies(url, config)
+        # No matching entry, so it falls back to an unauthenticated request.
+        self.assertEqual(command, ["curl", "-s", "-L"])
+
 
 if __name__ == "__main__":
     unittest.main()