From 6a8011f98360dad9dc6ac819ff64abcd281b9494 Mon Sep 17 00:00:00 2001 From: Rodrigo Gomes da Silva Date: Fri, 7 Aug 2026 02:25:59 -0300 Subject: [PATCH] fix: keep server.py under the 750-line guard after auth-reject close Move the close-on-reject decision into api.auth.check_auth_or_close so the added lines land outside server.py, restoring it to 749 lines. Trim the inline comment to a one-line docstring on the helper, per review feedback. The rejected-write connection-close test now patches api.auth.check_auth (the dependency the helper calls) instead of server.check_auth. --- api/auth.py | 8 ++++++++ server.py | 9 ++------- tests/test_rejected_write_connection_close.py | 3 ++- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/api/auth.py b/api/auth.py index 846c9945..3fa0e34d 100644 --- a/api/auth.py +++ b/api/auth.py @@ -1207,6 +1207,14 @@ def check_auth(handler, parsed) -> bool: return False +def check_auth_or_close(handler, parsed) -> bool: + """Check auth; close the connection when rejected so an unread body can't poison HTTP/1.1 reuse.""" + if not check_auth(handler, parsed): + handler.close_connection = True + return False + return True + + def _is_loopback(addr: str) -> bool: """Return True if *addr* is a loopback address (127.x.x.x, ::1, or ::ffff:127.x.x.x).""" import ipaddress as _ipaddress diff --git a/server.py b/server.py index 8205167b..fba5719e 100644 --- a/server.py +++ b/server.py @@ -100,7 +100,7 @@ from urllib.parse import urlparse logger = logging.getLogger(__name__) from api.request_logging import emit_request_log -from api.auth import check_auth, reset_trusted_auth_request_state +from api.auth import check_auth, check_auth_or_close, reset_trusted_auth_request_state from api.config import HOST, PORT, STATE_DIR, SESSION_DIR, DEFAULT_WORKSPACE from api.helpers import ( j, @@ -404,12 +404,7 @@ class Handler(BaseHTTPRequestHandler): _is_csp_report_post = ( parsed.path == "/api/csp-report" and self.command == "POST" ) - if not _is_csp_report_post and not check_auth(self, parsed): - # Authentication can reject an unsafe request before its body is - # consumed. Reusing that HTTP/1.1 connection would make the - # unread body prefix the next request line. - self.close_connection = True - return + if not _is_csp_report_post and not check_auth_or_close(self, parsed): return result = route_func(self, parsed) if result is False: return j(self, {'error': 'not found'}, status=404) diff --git a/tests/test_rejected_write_connection_close.py b/tests/test_rejected_write_connection_close.py index c3cd9fd7..ea004f2e 100644 --- a/tests/test_rejected_write_connection_close.py +++ b/tests/test_rejected_write_connection_close.py @@ -10,6 +10,7 @@ from typing import Any, cast def test_auth_rejected_write_closes_connection(monkeypatch): + import api.auth as auth import server handler = SimpleNamespace( @@ -20,7 +21,7 @@ def test_auth_rejected_write_closes_connection(monkeypatch): monkeypatch.setattr(server, "reset_trusted_auth_request_state", lambda _handler: None) monkeypatch.setattr(server, "get_profile_cookie", lambda _handler: None) monkeypatch.setattr(server, "clear_request_profile", lambda: None) - monkeypatch.setattr(server, "check_auth", lambda _handler, _parsed: False) + monkeypatch.setattr(auth, "check_auth", lambda _handler, _parsed: False) def route_must_not_run(_handler, _parsed): raise AssertionError("route ran after auth rejection")