fix(csrf): replay request body consumed during token validation #1

Merged
lundberg merged 1 commit from fix/csrf-body-replay into main 2026-06-10 11:08:57 +00:00
Owner

The CSRF middleware read the request body via request.form() to extract
the csrf_token form field, then handed the already-consumed ASGI receive
to the downstream app. The endpoint's own request.form()/body() then
blocked indefinitely waiting for body bytes that would never arrive,
hanging the request until the client disconnected (ClientDisconnect).

Only native form POSTs hit this path: htmx requests carry the token in
the X-CSRF-Token header and skip the body read. The OIDC consent form is
a plain form with the token in the body, so authorization consent hung.

Buffer the body when falling back to the form token and replay it to the
downstream app via a wrapped receive. Header-token requests are
unchanged. Adds a regression test where the endpoint reads the body after
body-token CSRF validation.

Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com

The CSRF middleware read the request body via request.form() to extract the csrf_token form field, then handed the already-consumed ASGI receive to the downstream app. The endpoint's own request.form()/body() then blocked indefinitely waiting for body bytes that would never arrive, hanging the request until the client disconnected (ClientDisconnect). Only native form POSTs hit this path: htmx requests carry the token in the X-CSRF-Token header and skip the body read. The OIDC consent form is a plain form with the token in the body, so authorization consent hung. Buffer the body when falling back to the form token and replay it to the downstream app via a wrapped receive. Header-token requests are unchanged. Adds a regression test where the endpoint reads the body after body-token CSRF validation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
lundberg added 1 commit 2026-06-10 11:07:49 +00:00
The CSRF middleware read the request body via request.form() to extract
the csrf_token form field, then handed the already-consumed ASGI receive
to the downstream app. The endpoint's own request.form()/body() then
blocked indefinitely waiting for body bytes that would never arrive,
hanging the request until the client disconnected (ClientDisconnect).

Only native form POSTs hit this path: htmx requests carry the token in
the X-CSRF-Token header and skip the body read. The OIDC consent form is
a plain form with the token in the body, so authorization consent hung.

Buffer the body when falling back to the form token and replay it to the
downstream app via a wrapped receive. Header-token requests are
unchanged. Adds a regression test where the endpoint reads the body after
body-token CSRF validation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
lundberg merged commit 3c5451b9c2 into main 2026-06-10 11:08:57 +00:00
lundberg deleted branch fix/csrf-body-replay 2026-06-10 11:08:57 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lundberg/porchlight#1
No description provided.