From 1d6c8a477b7bf586d5b17c7439274ede6cd89705 Mon Sep 17 00:00:00 2001 From: BigDataDZ <76271875+BigDataDZ@users.noreply.github.com> Date: Sat, 24 Oct 2026 15:31:37 +0800 Subject: [PATCH] fix(doctor): validate the GitHub host of release remotes Signed-off-by: BigDataDZ <76271875+BigDataDZ@users.noreply.github.com> --- loopx/doctor_git.py | 28 +++++++++-- tests/test_doctor_install_freshness.py | 64 ++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 4 deletions(-) diff --git a/loopx/doctor_git.py b/loopx/doctor_git.py index ecc59d90ae..5173ee11cd 100644 --- a/loopx/doctor_git.py +++ b/loopx/doctor_git.py @@ -8,6 +8,7 @@ from enum import Enum from pathlib import Path from typing import Any +from urllib.parse import urlsplit class GitRevisionRelation(str, Enum): @@ -164,10 +165,29 @@ def _is_ancestor(ancestor: str, descendant: str) -> bool | None: def _github_repository_from_remote_url(value: Any) -> str | None: - text = str(value or "").strip().removesuffix(".git") - match = re.search( - r"github\.com(?::|/)([^/\s]+/[^/\s]+)$", text, flags=re.IGNORECASE - ) + text = str(value or "").strip() + if any(character.isspace() for character in text): + return None + if "://" in text: + try: + url = urlsplit(text) + # Access validates explicit ports, which hostname alone does not. + _ = url.port + if (url.scheme not in {"http", "https", "git", "ssh", "git+ssh", "ssh+git"} + or url.hostname != "github.com" or url.query or url.fragment): + return None + except ValueError: + return None + path = url.path.removeprefix("/") + else: + # Git's SCP syntax has no URL authority; anchor its host separately. + remote = re.fullmatch( + r"(?:[^/@:\s]+@)?github\.com:(.+)", text, flags=re.IGNORECASE, + ) + if remote is None: + return None + path = remote.group(1) + match = re.fullmatch(r"([^/\s?#]+/[^/\s?#]+)", path.removesuffix(".git")) return match.group(1).lower() if match else None diff --git a/tests/test_doctor_install_freshness.py b/tests/test_doctor_install_freshness.py index dc44d3300e..2918e989e7 100644 --- a/tests/test_doctor_install_freshness.py +++ b/tests/test_doctor_install_freshness.py @@ -526,3 +526,67 @@ def test_trusted_release_ref_matches_manifest_repository(tmp_path: Path) -> None ) is None ) + + +@pytest.mark.parametrize( + ("remote_url", "github_host"), + [ + ("https://github.com/example/project.git", True), + ("http://github.com/example/project", True), + ("git://github.com/example/project.git", True), + ("ssh://git@github.com:22/example/project.git", True), + ("git+ssh://git@github.com/example/project.git", True), + ("ssh+git://git@github.com/example/project.git", True), + ("git@github.com:example/project.git", True), + ("github.com:example/project", True), + ("https://GITHUB.COM/example/project.git", True), + ("https://notgithub.com/example/project.git", False), + ("git@notgithub.com:example/project.git", False), + ("https://example.invalid/github.com/example/project.git", False), + ("https://github.com.example.invalid/github.com/example/project.git", False), + ("https://github.com@example.invalid/github.com/example/project.git", False), + ("file:///tmp/github.com/example/project.git", False), + ("https://github.com/other/project?next=github.com/example/project.git", False), + ("https://github.com/other/project#github.com/example/project.git", False), + pytest.param("https://git\thub.com/example/project.git", False, id="embedded-tab"), + pytest.param("https://github.com:invalid/example/project.git", False, id="invalid-port"), + pytest.param("https://github.com:65536/example/project.git", False, id="out-of-range-port"), + ], +) +def test_only_exact_github_remote_hosts_supply_install_freshness( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch, + remote_url: str, github_host: bool, +) -> None: + # A hook or outer checkout must not select the synthetic repository's index. + for key in ("GIT_DIR", "GIT_WORK_TREE", "GIT_COMMON_DIR", "GIT_INDEX_FILE"): + monkeypatch.delenv(key, raising=False) + _git(tmp_path, "init") + _git(tmp_path, "config", "user.email", "loopx@example.invalid") + _git(tmp_path, "config", "user.name", "LoopX Test") + installed = _commit(tmp_path, "installed") + latest = _commit(tmp_path, "latest") + _git(tmp_path, "remote", "add", "origin", remote_url) + _git(tmp_path, "update-ref", "refs/remotes/origin/main", latest) + + trusted = trusted_release_ref_for_root( + tmp_path, repository="example/project", ref="main", + ) + if github_host: + assert trusted is not None + assert trusted["git_commit"] == latest + assert trusted["label"] == "example/project@main" + else: + assert trusted is None + + relation = git_revision_relation( + tmp_path, installed_commit=installed, comparison_commit=latest, + ) + freshness = _freshness( + tmp_path, installed_commit=installed, comparison_commit=latest, + revision_relation=relation, + freshness_commit=trusted["git_commit"] if trusted else None, + freshness_relation=relation if trusted else None, + ) + assert freshness["status"] == ("stale" if github_host else "fresh") + assert freshness["requires_upgrade"] is github_host + assert freshness["freshness_source_git_commit"] == (latest if github_host else None)