aio-libs/aiohttp · error · InvalidHeader

Invalid HTTP header

Error message

Invalid HTTP header: {hdr!r}

What it means

Raised by the header parser when a header line cannot be split on ':' (line.split(b':', 1) raises ValueError), i.e. the line contains no colon at all. RFC 9112 requires every field line be 'field-name ":" OWS field-value'. The offending raw line (bytes) is included in the error.

Solutions

  1. Inspect the raw request/response bytes around the reported line.
  2. Ensure every header line is 'name: value' with a literal colon.
  3. Validate at the trust boundary (reverse proxy/WAF) before aiohttp sees the bytes.

Example fix

# before
sock.send(b'GET / HTTP/1.1\r\nHost example.com\r\n\r\n')

# after
sock.send(b'GET / HTTP/1.1\r\nHost: example.com\r\n\r\n')
Defensive patterns

Strategy: validation

Validate before calling

def header_line_ok(line: bytes) -> bool:
    # strict: name must be a token and a colon must separate name/value
    return b':' in line and not line[:1].isspace()

Type guard

def has_colon_separator(line: bytes) -> bool:
    return b':' in line

Try / catch

from aiohttp.http_exceptions import InvalidHeader, BadHttpMessage
try:
    await parser.feed_data(raw)
except (InvalidHeader, BadHttpMessage) as e:
    # close the malformed connection, log the peer
    ...

Prevention

When it happens

Trigger: A client or server sends a header line with no ':' separator, e.g. 'Host example.com' (space instead of colon), or a stray line fed to the parser.

Common situations: Hand-crafted HTTP via raw sockets, broken/upstream proxies injecting malformed lines, HTTP-smuggling probes, or non-HTTP data being fed into the parser.

Related errors


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

Appendix: source

Thrown at aiohttp/http_parser.py:181

    def __init__(self, max_field_size: int = 8190, lax: bool = False) -> None:
        self.max_field_size = max_field_size
        self._lax = lax

    def parse_headers(self, lines: list[bytes]) -> tuple[HeadersDictProxy, RawHeaders]:
        headers: CIMultiDict[str] = CIMultiDict()
        # note: "raw" does not mean inclusion of OWS before/after the field value
        raw_headers = []

        lines_idx = 0
        line = lines[lines_idx]
        line_count = len(lines)

        while line:
            # Parse initial header name : value pair.
            try:
                bname, bvalue = line.split(b":", 1)
            except ValueError:
                raise InvalidHeader(line) from None

            if len(bname) == 0:
                raise InvalidHeader(bname)

            # https://www.rfc-editor.org/rfc/rfc9112.html#section-5.1-2
            if {bname[0], bname[-1]} & {32, 9}:  # {" ", "\t"}
                raise InvalidHeader(line)

            bvalue = bvalue.lstrip(b" \t")
            name = bname.decode("utf-8", "surrogateescape")
            if not TOKENRE.fullmatch(name):
                raise InvalidHeader(bname)

            # next line
            lines_idx += 1
            line = lines[lines_idx]

            # consume continuation lines

View on GitHub (pinned to d041d4d0fd)