aio-libs/aiohttp · error · ValueError
Invalid boundary , expected
Error message
Invalid boundary {chunk!r}, expected {self._boundary!r} What it means
In _read_boundary, the line read between parts must equal either the boundary or the closing boundary ('--BOUNDARY--'). If it matches neither (and is not a recoverable epilogue/parent-boundary case), ValueError is raised showing the actual vs expected boundary.
Solutions
- Confirm the boundary token in the body exactly matches the Content-Type boundary parameter (including dashes and case).
- Ensure nested multipart uses distinct boundaries that do not collide.
- Do not let proxies rewrite multipart bodies piecemeal.
- Catch ValueError and abort with 400; optionally log chunk vs boundary to diagnose corruption.
Example fix
// before Content-Type: multipart/form-data; boundary=----A ...------B // after Content-Type: multipart/form-data; boundary=----A ...------A
Defensive patterns
Strategy: try-catch
Validate before calling
from aiohttp.multipart import parse_mimetype
def boundary_matches_header(content_type: str, body_first_line: bytes) -> bool:
mt = parse_mimetype(content_type)
expected = b'--' + mt.parameters.get('boundary', '').encode()
return body_first_line.rstrip(b'\r\n') == expected Try / catch
try:
async for part in reader:
process(part)
except ValueError as e:
if 'Invalid boundary' in str(e):
return web.Response(status=400, text='Boundary mismatch in multipart body')
raise Prevention
- Keep the boundary token identical in Content-Type and in every body line.
- Use distinct boundaries for nested multipart to avoid collisions.
- Do not let proxies rewrite multipart bodies without adjusting Content-Type.
When it happens
Trigger: A boundary line that differs from the one declared in Content-Type; mid-stream corruption; nested multipart where the inner boundary collides with or differs from the outer; a producer that changes boundary tokens between parts.
Common situations: Mismatched boundary strings between client and server; proxies rewriting the body but not the Content-Type boundary; truncated or interleaved multipart; attacks injecting fake boundaries.
Related errors
- Could not find starting boundary
- Reader did not read all the data or it is malformed
- Reading after EOF
- boundary missed for Content-Type
- boundary %r is too long (70 chars max)
AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11).
Data as JSON: /api/errors/c54332184ce8e026.
Report an issue: GitHub.
Appendix: source
Thrown at aiohttp/multipart.py:883
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 d041d4d0fd)