informed-decision/informed_decision/http_transport.py
tegwick 83849b75d4 Connect policy-gated browser review and audit runtime
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
2026-09-11 00:31:03 +02:00

82 lines
3.3 KiB
Python

"""Bounded JSON transport for fixed, deployment-owned HTTPS endpoints."""
from __future__ import annotations
import json
import ipaddress
from http.client import HTTPException
from urllib.error import HTTPError, URLError
from urllib.parse import urlsplit
from urllib.request import HTTPRedirectHandler, ProxyHandler, Request, build_opener
class TransportError(Exception):
"""Deliberately contains no response body, URL, token or request data."""
def fixed_origin(value: str, *, allow_internal_http=False) -> str:
parsed = urlsplit(value)
internal_http = False
if allow_internal_http and parsed.scheme == "http" and parsed.hostname:
try:
internal_http = ipaddress.ip_address(parsed.hostname).is_loopback
except ValueError:
internal_http = parsed.hostname.endswith((".svc", ".svc.cluster.local"))
if ((parsed.scheme != "https" and not internal_http) or not parsed.hostname or parsed.username
or parsed.password or parsed.path or parsed.query or parsed.fragment
or any(c.isspace() for c in value)):
raise ValueError("a fixed HTTPS origin without path or credentials is required")
return value
def https_origin(value: str) -> str:
return fixed_origin(value)
class _NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
class JSONTransport:
"""No redirects or ambient proxy credentials; five-second request timeout."""
def __init__(self, *, allow_internal_http=False) -> None:
self.allow_internal_http = allow_internal_http
self._opener = build_opener(ProxyHandler({}), _NoRedirect())
def request(self, method: str, url: str, *, headers=None, body=None) -> tuple[int, dict]:
parsed = urlsplit(url)
try:
fixed_origin(parsed.scheme + "://" + parsed.netloc,
allow_internal_http=self.allow_internal_http)
except ValueError:
raise TransportError("trusted transport required") from None
req = Request(url, method=method, data=body, headers=headers or {})
try:
try:
response = self._opener.open(req, timeout=5)
except HTTPError as exc:
response = exc
with response:
status = response.code
if 300 <= status < 400:
raise TransportError("upstream redirect refused")
raw = response.read(262145)
if len(raw) > 262144:
if status >= 400:
return status, {} # Refusal status is known; discard the body.
raise TransportError("upstream response too large")
try:
result = json.loads(raw)
except (ValueError, UnicodeError):
if status >= 400:
return status, {} # Flex Auth's caller gate uses plain HTTP errors.
raise
if not isinstance(result, dict):
if status >= 400:
return status, {}
raise TransportError("upstream object required")
return status, result
except (URLError, OSError, ValueError, HTTPException):
raise TransportError("upstream request failed") from None