Harden Calendar connector trust boundaries
This commit is contained in:
@@ -9,6 +9,12 @@ from dataclasses import dataclass, field
|
||||
from typing import Any, Mapping, Protocol
|
||||
|
||||
from defusedxml import ElementTree as SafeElementTree
|
||||
from govoplan_core.security.outbound_http import (
|
||||
OutboundHttpError,
|
||||
bounded_response_bytes,
|
||||
build_outbound_http_opener,
|
||||
validate_outbound_http_url,
|
||||
)
|
||||
|
||||
|
||||
class CalDAVError(RuntimeError):
|
||||
@@ -353,20 +359,31 @@ class CalDAVClient:
|
||||
def urllib_transport(method: str, url: str, headers: Mapping[str, str], body: bytes | None, timeout: int) -> tuple[int, Mapping[str, str], bytes]:
|
||||
url = validate_http_url(url)
|
||||
try:
|
||||
url = validate_outbound_http_url(url, label="CalDAV URL")
|
||||
request = urllib.request.Request( # noqa: S310 - URL is validated and origin-confined.
|
||||
url,
|
||||
data=body,
|
||||
headers=dict(headers),
|
||||
method=method,
|
||||
)
|
||||
opener = urllib.request.build_opener(_SameOriginRedirectHandler(url))
|
||||
opener = build_outbound_http_opener(_SameOriginRedirectHandler(url))
|
||||
with opener.open(request, timeout=timeout) as response: # noqa: S310 - validated CalDAV URL; redirects remain on origin. # nosec B310 # nosemgrep: python.lang.security.audit.dynamic-urllib-use-detected.dynamic-urllib-use-detected
|
||||
return response.status, dict(response.headers.items()), response.read()
|
||||
response_headers = dict(response.headers.items())
|
||||
return response.status, response_headers, bounded_response_bytes(
|
||||
response,
|
||||
headers=response_headers,
|
||||
label="CalDAV response",
|
||||
)
|
||||
except urllib.error.HTTPError as exc:
|
||||
return exc.code, dict(exc.headers.items()), exc.read()
|
||||
response_headers = dict(exc.headers.items())
|
||||
try:
|
||||
payload = bounded_response_bytes(exc, headers=response_headers, label="CalDAV error response")
|
||||
except OutboundHttpError as policy_exc:
|
||||
raise CalDAVError(f"{method} {url} failed: {policy_exc}") from policy_exc
|
||||
return exc.code, response_headers, payload
|
||||
except urllib.error.URLError as exc:
|
||||
raise CalDAVError(f"{method} {url} failed: {exc.reason}") from exc
|
||||
except ValueError as exc:
|
||||
except (OutboundHttpError, ValueError) as exc:
|
||||
raise CalDAVError(f"{method} {url} failed: {exc}") from exc
|
||||
|
||||
|
||||
@@ -542,7 +559,8 @@ class _SameOriginRedirectHandler(urllib.request.HTTPRedirectHandler):
|
||||
del fp, msg, headers
|
||||
try:
|
||||
candidate = validate_http_url(newurl)
|
||||
except CalDAVError:
|
||||
candidate = validate_outbound_http_url(candidate, label="CalDAV redirect URL")
|
||||
except (CalDAVError, OutboundHttpError):
|
||||
return None
|
||||
if _url_origin(urllib.parse.urlparse(candidate)) != self._source_origin:
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user