Repository navigation
Description
Hi! My team works with an application that uploads a multipart/form-data HTTP request. The multipart consists of two parts: An application/json part called "request" which contains information about the next part, and a video/mp4 part called "media". For our function tests, we use a Quart server that receives the multipart requests and validates them. Recently, our virtual environment updated Werkzeug to 3.1.5 which caused our tests to fail. What happened was that the "media" part started with "\r\n" and is 2 bytes longer than expected.
We tried to create a minimal reproducible example of a client and server. The server worked with the application and the issue could be recreated, but not with the client. Even if the request the client sent was identical to the application, the same issue didn't happen. We proceeded to use Wireshark to monitor the network traffic, and noticed that the request would only fail when the TCP segments were split up in a specific way. What would happen is that we would only get "400 Bad Request" when a TCP segment ended with "\r\n\r\n" (or 0d 0a 0d 0a) and the next TCP segment started with MP4 data (00 00 00 18).
import json
import logging
from quart import Quart, request
app = Quart("werkzeug-bug-example-server")
@app.route("/token", methods=["POST"])
async def token():
return {"access_token": "fake_token", "expires_in": 3600}, 200
@app.route("/upload/<job_id>", methods=["POST"])
async def upload(job_id):
files = await request.files
logging.warning(files.keys())
if "request" not in files:
return "Missing multipart part 'request'", 400
if "media" not in files:
return "Missing multipart part 'media'", 400
req_content_type = files["request"].content_type
if req_content_type != "application/json":
return (
"Invalid content-type for 'request' file, "
f"expected application/json, got {req_content_type}"
), 400
req = files["request"].read()
media_content_type = files["media"].content_type
if media_content_type != "video/mp4":
return (
"Invalid content-type for 'media' file, "
f"expected video/mp4, got {media_content_type}"
), 400
media = files["media"].read()
req_json = json.loads(req)
if "params" not in req_json:
return "Request json did not contain 'params'", 400
if "mediaContentLength" not in req_json["params"]:
return "Parameter 'params' did not contain 'mediaContentLength'", 400
media_length = len(media)
if media_length != req_json["params"]["mediaContentLength"]:
if media[:2] == b"\r\n":
logging.warning(f"Media content starts with CRLR: {media}")
return (
"Invalid media length, expected "
f"{req_json['params']['mediaContentLength']}, got {media_length}"
), 400
return "Successful upload", 200
if __name__ == '__main__':
app.run(host="0.0.0.0", port=8080)I included two Wireshark capture files. example1.pcapng only includes multipart requests where the server returned "400 Bad Request". example2.pcapng includes both requests where the server returned "200 OK" and "400 Bad Request". To check the TCP segments, press on a capture with protocol "MP4", then click on "Reassembled TCP Segments".
We expect that when the server splits the multipart that the "media" part does not start with "\r\n". Note: Downgrading to Werkzeug 3.1.4 solves the issue.
Environment:
- Python version: 3.11.2
- Werkzeug version: 3.1.5
example1.pcapng.gz
example2.pcapng.gz

Activity
pgjones commentedon Jan 20, 2026
Fixed by #3089
AnteKevo commentedon Jan 20, 2026
I tested our function tests with #3089 and can confirm that it works. So I am closing this issue.