diff --git a/src/vocalinux/gateway_embed/health.py b/src/vocalinux/gateway_embed/health.py index d2d0f72..ded60bf 100644 --- a/src/vocalinux/gateway_embed/health.py +++ b/src/vocalinux/gateway_embed/health.py @@ -7,6 +7,8 @@ import urllib.error import urllib.request from dataclasses import dataclass +from .urls import require_http_url + logger = logging.getLogger(__name__) DEFAULT_BASE_URL = "http://127.0.0.1:8765" @@ -23,8 +25,9 @@ class HealthSnapshot: def _get_status(url: str, *, timeout: float) -> tuple[int | None, str]: - request = urllib.request.Request(url, method="GET") try: + request = urllib.request.Request(require_http_url(url), method="GET") + # nosemgrep: python.lang.security.audit.dynamic-urllib-use-detected.dynamic-urllib-use-detected -- scheme checked by require_http_url with urllib.request.urlopen(request, timeout=timeout) as response: return int(response.status), "" except urllib.error.HTTPError as exc: diff --git a/src/vocalinux/gateway_embed/pairing.py b/src/vocalinux/gateway_embed/pairing.py index d529544..d7573ad 100644 --- a/src/vocalinux/gateway_embed/pairing.py +++ b/src/vocalinux/gateway_embed/pairing.py @@ -10,7 +10,7 @@ from dataclasses import dataclass from typing import Any, Optional from ..common_types import Readable -from .urls import is_unusable_phone_url, reject_loopback_url +from .urls import is_unusable_phone_url, reject_loopback_url, require_http_url logger = logging.getLogger(__name__) @@ -61,7 +61,8 @@ def _authorized_get( headers = {} if token: headers["Authorization"] = f"Bearer {token}" - request = urllib.request.Request(url, headers=headers, method="GET") + request = urllib.request.Request(require_http_url(url), headers=headers, method="GET") + # nosemgrep: python.lang.security.audit.dynamic-urllib-use-detected.dynamic-urllib-use-detected -- scheme checked by require_http_url with urllib.request.urlopen(request, timeout=timeout) as response: return _read_capped(response, limit) diff --git a/src/vocalinux/gateway_embed/urls.py b/src/vocalinux/gateway_embed/urls.py index bed871d..d12b492 100644 --- a/src/vocalinux/gateway_embed/urls.py +++ b/src/vocalinux/gateway_embed/urls.py @@ -182,3 +182,14 @@ def reject_loopback_url(url: str | None) -> str | None: if is_unusable_phone_url(url): return None return str(url).strip() + + +def require_http_url(url: str) -> str: + """Return *url* when it is an http(s) URL, else raise ``ValueError``. + + ``urllib.request.urlopen`` also opens ``file:`` and ``ftp:`` URLs, so every + gateway request passes its URL through here before it is opened. + """ + if urlparse(url).scheme not in ("http", "https"): + raise ValueError(f"gateway URL must use http or https: {url!r}") + return url