mirror of
https://github.com/github/spec-kit.git
synced 2026-08-03 06:26:30 +08:00
fix(github-http): return None on malformed host in resolve_github_release_asset_api_url (#3715)
Accessing the parsed authority (via urlparse/.hostname) raises ValueError on a malformed bracketed host, e.g. https://[not-an-ip]/..., mirroring the existing .port guard below. download_url is server-controlled (a catalog download_url payload), so the function's resolve-or-return-None contract must hold rather than leaking a raw traceback to the caller. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -102,8 +102,17 @@ def resolve_github_release_asset_api_url(
|
||||
|
||||
from specify_cli._download_security import read_response_limited
|
||||
|
||||
parsed = urlparse(download_url)
|
||||
hostname = (parsed.hostname or "").lower()
|
||||
# Accessing ``.hostname`` (like ``.port`` below) raises ValueError on a
|
||||
# malformed authority, e.g. an invalid bracketed IPv6 host
|
||||
# ``https://[not-an-ip]/...``. The function's contract is to return None for
|
||||
# anything it can't resolve, not to raise, so guard the read. ``download_url``
|
||||
# is server-controlled here (a catalog ``download_url`` payload), so a
|
||||
# malformed value must not leak a raw traceback past the caller.
|
||||
try:
|
||||
parsed = urlparse(download_url)
|
||||
hostname = (parsed.hostname or "").lower()
|
||||
except ValueError:
|
||||
return None
|
||||
parts = [unquote(part) for part in parsed.path.strip("/").split("/")]
|
||||
|
||||
is_ghes = (
|
||||
|
||||
@@ -292,6 +292,23 @@ class TestResolveGitHubReleaseAssetApiUrl:
|
||||
assert result is None
|
||||
assert called == []
|
||||
|
||||
def test_returns_none_on_malformed_host(self):
|
||||
"""A malformed authority (e.g. an invalid bracketed IPv6 host) returns
|
||||
None, not a ValueError (contract: resolve or return None, never raise)."""
|
||||
called = []
|
||||
|
||||
def open_never(url, timeout=None, extra_headers=None):
|
||||
called.append(url)
|
||||
raise AssertionError("open_url_fn must not be called")
|
||||
|
||||
result = resolve_github_release_asset_api_url(
|
||||
"https://[not-an-ip]/o/r/releases/download/v1/ext.zip",
|
||||
open_never,
|
||||
github_hosts=("ghes.example",),
|
||||
)
|
||||
assert result is None
|
||||
assert called == []
|
||||
|
||||
def test_passthrough_for_unlisted_ghes_api_asset_url(self):
|
||||
"""A direct GHES /api/v3 asset URL passes through even when the host is
|
||||
not allowlisted: passthrough issues no API request, and the download
|
||||
|
||||
Reference in New Issue
Block a user