aio-libs/aiohttp · error · ValueError

boundary value contains invalid characters

Error message

boundary value contains invalid characters

What it means

The _boundary_value property formats the boundary for the Content-Type header. If the bytes are not a bare token (matching the tchar regex) AND they contain a character invalid even inside a quoted-string (matched by _invalid_qdtext_char_regex), ValueError is raised because the value cannot be serialized either as a token or as a quoted string.

Solutions

  1. Restrict boundaries to RFC 2046 bchars: A-Za-z0-9 and '()+,-./_:;=?\''.
  2. Let MultipartWriter autogenerate the boundary.
  3. Sanitize the candidate boundary: re.sub(r'[^A-Za-z0-9\'()+,-./_:;=?]', '', value).
  4. Unit-test boundary serialization by constructing the writer and reading its Content-Type header.

Example fix

// before
mw = MultipartWriter(boundary='my boundary with space')

// after
mw = MultipartWriter(boundary='my_boundary_with_underscore')
Defensive patterns

Strategy: validation

Validate before calling

import re
_VALID_BOUNDARY = re.compile(r"[A-Za-z0-9'()+,./_:;=?-]+\Z")

def is_serializable_boundary(value: str) -> bool:
    return bool(_VALID_BOUNDARY.match(value))

Type guard

import re
_TOKEN = re.compile(r"[A-Za-z0-9'()+,./_:;=?-]+\Z")
def is_token_boundary(value: object) -> bool:
    return isinstance(value, str) and bool(_TOKEN.match(value))

Try / catch

try:
    mw = MultipartWriter(boundary=candidate)
    _ = mw.content_type  # forces _boundary_value formatting
except ValueError as e:
    if 'invalid characters' in str(e):
        mw = MultipartWriter()  # fall back to safe autogenerated boundary
    else:
        raise

Prevention

When it happens

Trigger: A boundary containing control characters, NUL, bare backslash sequences that break quoted-string rules, or other bytes outside the union of tchar and valid qdtext/obs-text.

Common situations: User-supplied boundaries with whitespace, control chars, or delimiters; binary boundaries not filtered through the bchars set; boundaries copied from opaque tokens.

Understand the failure class

Related errors


AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11). Data as JSON: /api/errors/4ebdcc16d0a59897. Report an issue: GitHub.

Appendix: source

Thrown at aiohttp/multipart.py:992

        # 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 d041d4d0fd)