diff --git a/scripts/ci/sandboxed_web_e2e.py b/scripts/ci/sandboxed_web_e2e.py index 682202fa..506cc044 100644 --- a/scripts/ci/sandboxed_web_e2e.py +++ b/scripts/ci/sandboxed_web_e2e.py @@ -26,14 +26,12 @@ RESULT_MARKER = "SANDBOXED_WEB_E2E_RESULT" -class NoRedirectHandler(urllib.request.HTTPErrorProcessor): +class NoRedirectHandler(urllib.request.HTTPRedirectHandler): """Explicitly disable redirects to prevent SSRF bypasses via 301/302 to local IPs.""" - def http_response(self, request, response): - """Return the original HTTP response without following redirects.""" - return response - - https_response = http_response + def redirect_request(self, req, fp, code, msg, headers, newurl): + """Raise an HTTPError instead of following the redirect.""" + raise urllib.error.HTTPError(req.full_url, code, msg, headers, fp) @dataclass diff --git a/tests/test_sandboxed_web_e2e.py b/tests/test_sandboxed_web_e2e.py index 38f5f8bf..7bd9a2a8 100644 --- a/tests/test_sandboxed_web_e2e.py +++ b/tests/test_sandboxed_web_e2e.py @@ -228,16 +228,16 @@ def open(self, url, timeout): assert sandboxed_web_e2e.tail_text(log_path).splitlines()[0] == "line-10" -def test_no_redirect_handler_returns_redirect_without_following(): - """Readiness checks must not follow redirects to attacker-controlled internal URLs.""" - - class RedirectResponse: - status = 302 +def test_no_redirect_handler_raises_httperror_without_following(): + """Readiness checks must raise HTTPError on redirects to prevent attacker-controlled internal URLs.""" + import urllib.error request = sandboxed_web_e2e.urllib.request.Request("https://example.test/ready") - response = RedirectResponse() - assert sandboxed_web_e2e.NoRedirectHandler().http_response(request, response) is response + with pytest.raises(urllib.error.HTTPError) as exc_info: + sandboxed_web_e2e.NoRedirectHandler().redirect_request(request, None, 302, "Found", {}, "http://127.0.0.1") + + assert exc_info.value.code == 302 def test_wait_for_url_returns_false_after_timeout(monkeypatch, tmp_path):