aio-libs/aiohttp · error · ValueError

Invalid boundary {chunk!r}, expected {self._boundary!r}

Error message

Invalid boundary {chunk!r}, expected {self._boundary!r}

What it means

Raised by MultipartReader._read_boundary() when, after the first part, the reader reads a delimiter line that is neither the expected `--<boundary>` (start of next part) nor `--<boundary>--` (final delimiter). This indicates the stream is corrupted or the boundary has drifted out of sync.

Source

Thrown at aiohttp/multipart.py:879

            pass
        elif chunk == self._boundary + b"--":
            self._at_eof = True
            epilogue = await self._readline()
            next_line = await self._readline()

            # the epilogue is expected and then either the end of input or the
            # parent multipart boundary, if the parent boundary is found then
            # it should be marked as unread and handed to the parent for
            # processing
            if next_line[:2] == b"--":
                self._unread.append(next_line)
            # otherwise the request is likely missing an epilogue and both
            # lines should be passed to the parent for processing
            # (this handles the old behavior gracefully)
            else:
                self._unread.extend([next_line, epilogue])
        else:
            raise ValueError(f"Invalid boundary {chunk!r}, expected {self._boundary!r}")

    async def _read_headers(self) -> HeadersDictProxy:
        lines = []
        while True:
            chunk = await self._content.readline(max_line_length=self._max_field_size)
            chunk = chunk.rstrip(b"\r\n")
            lines.append(chunk)
            if not chunk:
                break
            if len(lines) > self._max_headers:
                raise BadHttpMessage("Too many headers received")
        parser = HeadersParser(max_field_size=self._max_field_size)
        headers, _ = parser.parse_headers(lines)
        return headers

    async def _maybe_release_last_part(self) -> None:
        """Ensures that the last read body part is read completely."""
        if self._last_part is not None:

View on GitHub (pinned to c0ef574e29)

Solutions

  1. Confirm the Content-Type boundary exactly equals the delimiters inside the body.
  2. Ensure upstream sets correct Content-Length on each part or uses chunked framing consistently.
  3. Catch ValueError and abort the multipart parse with a 400/502.

Example fix

// before
async for part in reader:  # mid-body delimiter mismatch -> ValueError
    ...
// after
try:
    async for part in reader:
        ...
except ValueError as e:
    return web.Response(status=400, text=f'malformed multipart: {e}')
Defensive patterns

Strategy: try-catch

Try / catch

try:
    async for part in reader:
        handle(part)
except ValueError as e:
    if 'Invalid boundary' in str(e):
        return web.Response(status=400, text='multipart stream corrupted')
    raise

Prevention

When it happens

Trigger: Iterating parts with `await reader.next()` and the bytes between parts do not match the configured boundary. Caused by a body whose internal delimiters differ from the Content-Type boundary, or by part bodies that leaked across the boundary due to a wrong Content-Length.

Common situations: Mismatched boundary between Content-Type and body; a part with an incorrect Content-Length causing the reader to start mid-part; nested multipart with wrong reader; man-in-the-middle corruption.

Related errors


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