You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
restore_payload drops the body of multipart/form-data and x-www-form-urlencoded PUT/PATCH requests
Summary
restore_payload() only reconstructs a consumed form body when request.method == "POST". For any other method that legitimately carries a form body — notably PUT and PATCH — it returns the already-emptied request.data, so the body is silently lost. This surfaces when rolo is used to proxy requests (e.g. the API Gateway HTTP API AWS_PROXY integration in LocalStack): a multipart/form-dataPUT/PATCH reaches the target with an empty body while Content-Length is preserved.
The HTTP spec places no restriction tying multipart/form-data (or any media type) to POST — RFC 9110 lets PUT/PATCH carry any representation, and clients routinely send multipart via fetch(url, {method: 'PUT', body: formData}). The POST-only assumption here appears to trace back to HTML <form> (which only supports GET/POST), not to HTTP itself.
For a form content-type, Werkzeug's request.data is empty (the body is reserved for form parsing), so the method != "POST" early-return yields b"" for PUT/PATCH instead of re-encoding request.form + request.files.
POST len=174 # reconstructed
PUT len=0 # body lost
PATCH len=0 # body lost
Impact
Observed end-to-end on LocalStack 2026.06.0 (HTTP API v2 → Lambda AWS_PROXY): a multipart/form-dataPUT (and PATCH) is delivered to the Lambda with "body": "" while Content-Length is forwarded, so the handler sees an empty/truncated form (multipart: NextPart: EOF). The same request as POST works. Any rolo-based proxy path for non-POST form uploads is affected.
Suggested fix
Reconstruct for every method that carries a form body, or key off content-type rather than method:
(Or drop the method check entirely and gate solely on request.mimetype, since the reconstruction is a no-op for non-form bodies.)
Notes
restore_payload was last touched in fix restore payload #13, which fixed the multipart-vs-urlencoded re-encoding but left the method != "POST" guard in place.
restore_payloaddrops the body ofmultipart/form-dataandx-www-form-urlencodedPUT/PATCH requestsSummary
restore_payload()only reconstructs a consumed form body whenrequest.method == "POST". For any other method that legitimately carries a form body — notablyPUTandPATCH— it returns the already-emptiedrequest.data, so the body is silently lost. This surfaces when rolo is used to proxy requests (e.g. the API Gateway HTTP APIAWS_PROXYintegration in LocalStack): amultipart/form-dataPUT/PATCHreaches the target with an empty body whileContent-Lengthis preserved.The HTTP spec places no restriction tying
multipart/form-data(or any media type) toPOST— RFC 9110 letsPUT/PATCHcarry any representation, and clients routinely send multipart viafetch(url, {method: 'PUT', body: formData}). ThePOST-only assumption here appears to trace back to HTML<form>(which only supports GET/POST), not to HTTP itself.Affected code
rolo/request.py—restore_payload(rolo 0.8.3):For a form content-type, Werkzeug's
request.datais empty (the body is reserved for form parsing), so themethod != "POST"early-return yieldsb""forPUT/PATCHinstead of re-encodingrequest.form+request.files.Reproduction
Output (rolo 0.8.3):
Impact
Observed end-to-end on LocalStack
2026.06.0(HTTP API v2 → LambdaAWS_PROXY): amultipart/form-dataPUT(andPATCH) is delivered to the Lambda with"body": ""whileContent-Lengthis forwarded, so the handler sees an empty/truncated form (multipart: NextPart: EOF). The same request asPOSTworks. Any rolo-based proxy path for non-POST form uploads is affected.Suggested fix
Reconstruct for every method that carries a form body, or key off content-type rather than method:
(Or drop the method check entirely and gate solely on
request.mimetype, since the reconstruction is a no-op for non-form bodies.)Notes
restore_payloadwas last touched in fix restore payload #13, which fixed the multipart-vs-urlencoded re-encoding but left themethod != "POST"guard in place.