aio-libs/aiohttp · error · BadHttpMessage

Request has duplicate `chunked` Transfer-Encoding

Error message

Request has duplicate `chunked` Transfer-Encoding

What it means

Raised by HttpRequestParser._is_chunked_te (aiohttp/http_parser.py:745) when the Transfer-Encoding value, split on commas, contains 'chunked' more than once. RFC 9112 section 7.1 forbids applying the chunked coding more than once; doing so is a request-smuggling vector against parsers that deduplicate inconsistently. Request-parser specific (the response parser uses a looser check that does not count duplicates).

Source

Thrown at aiohttp/http_parser.py:745

            path,
            version_o,
            headers,
            raw_headers,
            close,
            compression,
            upgrade,
            chunked,
            url,
        )

    def _is_chunked_te(self, te: str) -> bool:
        # https://www.rfc-editor.org/rfc/rfc9112#section-7.1-3
        # "A sender MUST NOT apply the chunked transfer coding more
        #  than once to a message body"
        parts = [p.strip(" \t") for p in te.split(",")]
        chunked_count = sum(1 for p in parts if p.isascii() and p.lower() == "chunked")
        if chunked_count > 1:
            raise BadHttpMessage("Request has duplicate `chunked` Transfer-Encoding")
        last = parts[-1]
        # .lower() transforms some non-ascii chars, so must check first.
        if last.isascii() and last.lower() == "chunked":
            return True
        # https://www.rfc-editor.org/rfc/rfc9112#section-6.3-2.4.3
        raise BadHttpMessage("Request has invalid `Transfer-Encoding`")


class HttpResponseParser(HttpParser[RawResponseMessage]):
    """Read response status line and headers.

    BadStatusLine could be raised in case of any errors in status line.
    Returns RawResponseMessage.
    """

    protocol: "ResponseHandler"

    # Lax mode should only be enabled on response parser.

View on GitHub (pinned to c0ef574e29)

Solutions

  1. Send 'Transfer-Encoding: chunked' exactly once, and only as the LAST coding in the list.
  2. Fix any proxy that appends 'chunked' redundantly; deduplicate before forwarding.
  3. Let aiohttp handle chunking automatically when you stream a body; do not set Transfer-Encoding manually.

Example fix

# before - chunked listed twice
headers['Transfer-Encoding'] = 'chunked, chunked'
# after - single chunked as the last coding
headers['Transfer-Encoding'] = 'chunked'
# or just stream the body and let aiohttp add TE: chunked
Defensive patterns

Strategy: validation

Validate before calling

def chunked_appears_once(te_value: str) -> bool:
    parts = [p.strip().lower() for p in te_value.split(',')]
    return parts.count('chunked') <= 1
te = headers.get('Transfer-Encoding')
if te is not None and not chunked_appears_once(te):
    raise ValueError('duplicate chunked in Transfer-Encoding')

Prevention

When it happens

Trigger: A request with 'Transfer-Encoding: chunked, chunked' or any TE value where two or more comma-separated codings equal 'chunked'. Even 'gzip, chunked, chunked' triggers it.

Common situations: Request-smuggling attacks, proxies that append 'chunked' without deduplicating, or buggy encoders that stack the coding. Distinct from error 133 (TE+CL conflict) - here only TE is present but chunked is duplicated.

Related errors


AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04). Data as JSON: /data/errors/4d72e200bc582d7f.json. Report an issue: GitHub.