aio-libs/aiohttp · error · RuntimeError

Invalid default charset

Error message

Invalid default charset

What it means

Raised by MultipartReader.next() when processing a multipart/form-data body that uses the _charset_ convention (RFC 7578 §4.6): the first part named '_charset_' declares the default charset for subsequent parts. aiohttp reads up to 32 bytes for that value; if more than 31 bytes are read it treats the charset as invalid/malicious and raises RuntimeError.

Source

Thrown at aiohttp/multipart.py:771

            await self._read_boundary()
        if self._at_eof:  # we just read the last boundary, nothing to do there
            # https://github.com/python/mypy/issues/17537
            return None  # type: ignore[unreachable]

        part = await self.fetch_next_part()
        # https://datatracker.ietf.org/doc/html/rfc7578#section-4.6
        if (
            self._last_part is None
            and self._mimetype.subtype == "form-data"
            and isinstance(part, BodyPartReader)
        ):
            _, params = parse_content_disposition(part.headers.get(CONTENT_DISPOSITION))
            if params.get("name") == "_charset_":
                # Longest encoding in https://encoding.spec.whatwg.org/encodings.json
                # is 19 characters, so 32 should be more than enough for any valid encoding.
                charset = await part.read_chunk(32)
                if len(charset) > 31:
                    raise RuntimeError("Invalid default charset")
                self._default_charset = charset.strip().decode()
                part = await self.fetch_next_part()
        self._last_part = part
        return self._last_part

    async def release(self) -> None:
        """Reads all the body parts to the void till the final boundary."""
        while not self._at_eof:
            item = await self.next()
            if item is None:
                break
            await item.release()

    async def fetch_next_part(
        self,
    ) -> Union["MultipartReader", BodyPartReader]:
        """Returns the next body part reader."""
        headers = await self._read_headers()

View on GitHub (pinned to c0ef574e29)

Solutions

  1. Reject the request with 400 — a >31-byte _charset_ value is not legitimate.
  2. Fix the client so the _charset_ part contains a real, short encoding name (e.g. 'utf-8').
  3. Wrap multipart parsing in try/except RuntimeError and treat it as a bad request.

Example fix

// before
async for part in request.multipart():  # _charset_ too long -> RuntimeError
    ...
// after
try:
    async for part in request.multipart():
        ...
except RuntimeError:
    return web.Response(status=400, text='malformed multipart form')
Defensive patterns

Strategy: try-catch

Try / catch

try:
    async for part in request.multipart():
        process(part)
except RuntimeError as e:
    if 'Invalid default charset' in str(e):
        return web.Response(status=400, text='malformed _charset_ field')
    raise

Prevention

When it happens

Trigger: Receiving a multipart/form-data request whose first part is named '_charset_' but whose body is longer than 31 bytes. This is almost always malformed or malicious input, since real charset names are short (longest in the WHATWG list is ~19 chars).

Common situations: Fuzzing/malicious clients; a buggy form generator that puts the wrong content into the _charset_ field; a client confusing the _charset_ field with an actual text value.

Related errors


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