From e7e880ad9264977a39ae4db5f52545985adc424c Mon Sep 17 00:00:00 2001 From: Harshit Sharma <66710144+harshitethic@users.noreply.github.com> Date: Sun, 25 Oct 2026 09:19:36 -0400 Subject: [PATCH 1/2] security(http): reject ambiguous Host headers --- backend/app/access_control.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/backend/app/access_control.py b/backend/app/access_control.py index 0767b31..dbe12ee 100644 --- a/backend/app/access_control.py +++ b/backend/app/access_control.py @@ -61,6 +61,11 @@ async def dispatch(self, request: Request, call_next): if request.method == "OPTIONS" and "access-control-request-method" in request.headers: return await call_next(request) + # Duplicate Host headers are ambiguous across proxies and ASGI servers. + # Do not let a different hop authorize a different authority. + if len(request.headers.getlist("host")) != 1: + return JSONResponse({"detail": "Exactly one Host header is required"}, status_code=400) + origin = request.headers.get("origin") if origin is not None and origin not in self.allowed_origins(): return JSONResponse({"detail": "Untrusted browser origin"}, status_code=403) From 142f3f431bbc526f587ebe8a2fdba11959446086 Mon Sep 17 00:00:00 2001 From: Harshit Sharma <66710144+harshitethic@users.noreply.github.com> Date: Sun, 25 Oct 2026 09:19:39 -0400 Subject: [PATCH 2/2] test(http): reject duplicate Host under both auth modes --- backend/tests/test_api_access.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/backend/tests/test_api_access.py b/backend/tests/test_api_access.py index ca08992..e0679c2 100644 --- a/backend/tests/test_api_access.py +++ b/backend/tests/test_api_access.py @@ -143,6 +143,16 @@ async def test_remote_with_valid_token_allowed_but_origin_still_checked(self): ) self.assertEqual(blocked.status_code, 403) + async def test_duplicate_host_headers_rejected_in_local_and_token_modes(self): + for env in ({}, {"PATCHPILOT_API_TOKEN": TOKEN}): + with self.subTest(token_enabled=bool(env)): + headers = [("Host", "localhost"), ("Host", "malicious.example")] + if env: + headers.append(("Authorization", "Bearer " + TOKEN)) + response = await self.request(headers=headers, env=env) + self.assertEqual(response.status_code, 400) + self.assertIn("Host header", response.text) + async def test_duplicate_auth_headers_refused(self): token = TOKEN response = await self.request(