aio-libs/aiohttp · error · ValueError

boundary value contains invalid characters

Error message

boundary value contains invalid characters

What it means

Raised by MultipartWriter._boundary_value when the boundary, not expressible as a bare HTTP token, must be quoted but contains characters that are invalid even inside a quoted-string (control chars or DEL, per the qdtext grammar). The boundary then cannot be serialized into a valid Content-Type header.

Source

Thrown at aiohttp/multipart.py:983

        # Refer to RFCs 7231, 7230, 5234.
        #
        # parameter      = token "=" ( token / quoted-string )
        # token          = 1*tchar
        # quoted-string  = DQUOTE *( qdtext / quoted-pair ) DQUOTE
        # qdtext         = HTAB / SP / %x21 / %x23-5B / %x5D-7E / obs-text
        # obs-text       = %x80-FF
        # quoted-pair    = "\" ( HTAB / SP / VCHAR / obs-text )
        # tchar          = "!" / "#" / "$" / "%" / "&" / "'" / "*"
        #                  / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
        #                  / DIGIT / ALPHA
        #                  ; any VCHAR, except delimiters
        # VCHAR           = %x21-7E
        value = self._boundary
        if re.match(self._valid_tchar_regex, value):
            return value.decode("ascii")  # cannot fail

        if re.search(self._invalid_qdtext_char_regex, value):
            raise ValueError("boundary value contains invalid characters")

        # escape %x5C and %x22
        quoted_value_content = value.replace(b"\\", b"\\\\")
        quoted_value_content = quoted_value_content.replace(b'"', b'\\"')

        return '"' + quoted_value_content.decode("ascii") + '"'

    @property
    def boundary(self) -> str:
        return self._boundary.decode("ascii")

    def append(self, obj: Any, headers: Mapping[str, str] | None = None) -> Payload:
        if headers is None:
            headers = CIMultiDict()

        if isinstance(obj, Payload):
            obj.headers.update(headers)
            return self.append_payload(obj)

View on GitHub (pinned to c0ef574e29)

Solutions

  1. Restrict the boundary to RFC 2046 token characters: alphanumeric plus `!#$%&'*+-.^_`|~`.
  2. Let MultipartWriter generate its own boundary (uuid4 hex) instead of passing a custom one.
  3. Sanitize any dynamic boundary with a whitelist regex before construction.

Example fix

// before
w = MultipartWriter(boundary='a\x00b')
// after
import re
safe = re.sub(rb'[^!#$%&\'*+\-.^_`|~\w]', b'', boundary)
w = MultipartWriter(boundary=safe.decode('ascii'))
Defensive patterns

Strategy: validation

Validate before calling

import re
token_re = re.compile(rb"\A[!#$%&'*+\-.^_`|~\w]+\Z")
if not token_re.match(boundary.encode('ascii')):
    raise ValueError('boundary must be token-safe or quotable')

Type guard

import re
_token = re.compile(rb"\A[!#$%&'*+\-.^_`|~\w]+\Z")

def is_valid_boundary(b: str) -> bool:
    return bool(_token.match(b.encode('ascii', 'ignore')))

Try / catch

try:
    writer = MultipartWriter(boundary=boundary)
except ValueError:
    writer = MultipartWriter()  # auto-generate a safe boundary

Prevention

When it happens

Trigger: Constructing a MultipartWriter whose boundary contains bytes outside the token charset AND outside the allowed qdtext range (e.g. NUL, CR, LF, other control characters). The token regex fails, so the code attempts to quote it, then the qdtext validity check fails.

Common situations: Boundary derived from arbitrary binary/uuid bytes that include control characters; a typo introducing a non-printable character; fuzz-generated boundary values.

Related errors


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