From be476cb7dc3486a19225250c66ef09f041d60bb0 Mon Sep 17 00:00:00 2001 From: Georges-Antoine Assi Date: Sun, 24 May 2026 15:46:50 -0400 Subject: [PATCH] Only set CSRF cookie on http.response.start ASGI spec only allows headers on the http.response.start message; appending Set-Cookie to body messages is out-of-spec and may break on some servers. Early-return for non-start messages. Co-Authored-By: Claude Opus 4.7 (1M context) --- backend/handler/auth/middleware/csrf_middleware.py | 4 ++++ backend/tests/handler/auth/test_csrf_middleware.py | 12 ++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/backend/handler/auth/middleware/csrf_middleware.py b/backend/handler/auth/middleware/csrf_middleware.py index 6c82e947b..937cf6cda 100644 --- a/backend/handler/auth/middleware/csrf_middleware.py +++ b/backend/handler/auth/middleware/csrf_middleware.py @@ -90,6 +90,10 @@ class CSRFMiddleware: await self.app(scope, receive, send) async def send(self, message: Message, send: Send, scope: Scope) -> None: + if message["type"] != "http.response.start": + await send(message) + return + request = Request(scope) csrf_cookie = request.cookies.get(self.cookie_name) current_user_id = request.user.id if request.user.is_authenticated else None diff --git a/backend/tests/handler/auth/test_csrf_middleware.py b/backend/tests/handler/auth/test_csrf_middleware.py index 8dd28a95d..fbd0c86a4 100644 --- a/backend/tests/handler/auth/test_csrf_middleware.py +++ b/backend/tests/handler/auth/test_csrf_middleware.py @@ -242,8 +242,12 @@ class TestCSRFMiddleware: def test_user_id_mismatch_fails(self) -> None: """Tokens issued for one user must not validate for another.""" + # We simulate two users by calling _generate_csrf_token with different IDs - mw = CSRFMiddleware(app=lambda s, r, se: None, secret="test") + async def noop_app(scope, receive, send): + pass + + mw = CSRFMiddleware(app=noop_app, secret="test") user1_token = mw._generate_csrf_token(user_id=1) # user1_token should not validate for user_id=2 @@ -293,6 +297,10 @@ class TestCSRFMiddleware: def test_bad_signature_returns_false(self) -> None: """_csrf_tokens_match should return False on BadSignature.""" - mw = CSRFMiddleware(app=lambda s, r, se: None, secret="test") + + async def noop_app(scope, receive, send): + pass + + mw = CSRFMiddleware(app=noop_app, secret="test") ok = mw._csrf_tokens_match("bad-token", "bad-token", user_id=None) assert ok is False