Skip to content

Multipart parsing fails occasionallyย #3088

Closed

Description

@AnteKevo

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

Image

Activity

  1. pgjones commented on Jan 20, 2026

    @pgjones
    Member

    Fixed by #3089

  2. AnteKevo commented on Jan 20, 2026

    @AnteKevo
    Author

    I tested our function tests with #3089 and can confirm that it works. So I am closing this issue.

  3. locked as resolved and limited conversation to collaborators on Feb 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions