aio-libs/aiohttp · error · ValueError
boundary %r is too long (70 chars max)
Error message
boundary %r is too long (70 chars max)
What it means
MultipartReader._get_boundary enforces the RFC 2046 limit of 70 characters on the boundary parameter. If the boundary string from the Content-Type exceeds 70 chars, ValueError is raised before any reading begins.
Solutions
- Shorten the boundary value to <= 70 characters (RFC 2046 allows up to 70).
- Use the boundary produced by MultipartWriter on the producing side, which respects the limit.
- Validate Content-Type boundary length on the server and return 400 before constructing the reader.
- Catch ValueError and reject the request as malformed.
Example fix
// before Content-Type: multipart/form-data; boundary=AAAAAAAAAA...70+chars...AAAAA // after Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxk
Defensive patterns
Strategy: validation
Validate before calling
MAX_BOUNDARY = 70
def assert_boundary_length(content_type: str) -> None:
mt = parse_mimetype(content_type)
b = mt.parameters.get('boundary')
if mt.type == 'multipart' and (not b or len(b) > MAX_BOUNDARY):
raise ValueError(f'boundary missing or too long (> {MAX_BOUNDARY})') Type guard
def is_valid_boundary_length(content_type: object) -> bool:
if not isinstance(content_type, str):
return False
mt = parse_mimetype(content_type)
b = mt.parameters.get('boundary')
return mt.type != 'multipart' or bool(b) and len(b) <= 70 Try / catch
try:
reader = MultipartReader(headers, content)
except ValueError as e:
if 'too long' in str(e):
return web.Response(status=400, text='Boundary exceeds 70 characters')
raise Prevention
- Keep boundary tokens <= 70 characters (30-50 is comfortable).
- Validate the Content-Type boundary length server-side before parsing.
- Use a producer that respects the RFC 2046 limit (MultipartWriter does).
When it happens
Trigger: A Content-Type header whose boundary= value is longer than 70 characters; typically a misconfigured or adversarial sender.
Common situations: Producers generating boundaries from long UUIDs concatenated with extra prefixes/suffixes; copy-paste of base64 blobs as boundary; fuzzing.
Related errors
- boundary missed for Content-Type
- boundary value contains invalid characters
- boundary should contain ASCII only chars
- content_type must be an instance of str. Got
- Could not find starting boundary
AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11).
Data as JSON: /api/errors/1880818c3e2a2de5.
Report an issue: GitHub.
Appendix: source
Thrown at aiohttp/multipart.py:841
max_field_size=self._max_field_size,
max_headers=self._max_headers,
max_size_error_cls=self._max_size_error_cls,
)
else:
return self.part_reader_cls(
self._boundary,
headers,
self._content,
subtype=self._mimetype.subtype,
default_charset=self._default_charset,
client_max_size=self._client_max_size,
max_size_error_cls=self._max_size_error_cls,
)
def _get_boundary(self) -> str:
boundary = self._mimetype.parameters["boundary"]
if len(boundary) > 70:
raise ValueError("boundary %r is too long (70 chars max)" % boundary)
return boundary
async def _readline(self) -> bytes:
if self._unread:
return self._unread.pop()
return await self._content.readline()
async def _read_until_first_boundary(self) -> None:
while True:
chunk = await self._readline()
if chunk == b"":
raise ValueError(f"Could not find starting boundary {self._boundary!r}")
chunk = chunk.rstrip()
if chunk == self._boundary:
return
elif chunk == self._boundary + b"--":
self._at_eof = TrueView on GitHub (pinned to d041d4d0fd)