aio-libs/aiohttp · error · BadHttpMessage
Duplicate '{name}' header found.
Error message
Duplicate '{name}' header found. What it means
Raised by HeadersParser.parse_headers (aiohttp/http_parser.py:237) in strict mode (request parser) when a header name appears more than once AND its lowercased name is in SINGLETON_HEADERS: content-length, content-location, content-range, content-type, etag, host, max-forwards, server, transfer-encoding, user-agent. RFC 9110 sections 8.1/8.3 require these appear at most once. The lax response parser skips this check because real-world servers (Google APIs, Werkzeug, etc.) commonly send duplicate Content-Type/Server.
Source
Thrown at aiohttp/http_parser.py:237
if line:
continuation = line[0] in (32, 9) # (' ', '\t')
else:
line = b""
break
bvalue = b"".join(bvalue_lst)
bvalue = bvalue.strip(b" \t")
value = bvalue.decode("utf-8", "surrogateescape")
# https://www.rfc-editor.org/rfc/rfc9110.html#section-5.5-5
if self._lax:
if "\n" in value or "\r" in value or "\x00" in value:
raise InvalidHeader(bvalue)
elif _FIELD_VALUE_FORBIDDEN_CTL_RE.search(value):
raise InvalidHeader(bvalue)
if not self._lax and name in headers and name.lower() in SINGLETON_HEADERS:
raise BadHttpMessage(f"Duplicate '{name}' header found.")
headers.add(name, value)
raw_headers.append((bname, bvalue))
return (HeadersDictProxy(headers), tuple(raw_headers))
def _is_supported_upgrade(headers: HeadersDictProxy) -> bool:
"""Check if the upgrade header is supported."""
u = headers.get(hdrs.UPGRADE, "")
# .lower() can transform non-ascii characters.
return u.isascii() and u.lower() in {"tcp", "websocket"}
class HttpParser(abc.ABC, Generic[_MsgT]):
lax: ClassVar[bool] = False
def __init__(
self,View on GitHub (pinned to c0ef574e29)
Solutions
- Send each singleton header exactly once; use assignment (headers[name] = value) rather than headers.add() for these fields.
- If you combine headers from multiple sources, deduplicate singleton names before sending.
- On the server side, aiohttp returns 400 automatically; log and alert on duplicates since they often indicate smuggling.
- Audit any proxy in the chain that may append a duplicate singleton.
Example fix
# before - .add() can stack duplicates
headers.add('Host', 'a.com')
headers.add('Host', 'b.com') # duplicate -> rejected
# after - assignment replaces
headers['Host'] = 'a.com' Defensive patterns
Strategy: validation
Validate before calling
from aiohttp.http_parser import SINGLETON_HEADERS
def has_duplicate_singletons(headers):
seen, dups = set(), set()
for k in headers.keys():
lk = k.lower()
if lk in SINGLETON_HEADERS:
(dups if lk in seen else seen).add(lk)
return dups
if has_duplicate_singletons(outgoing):
raise ValueError('duplicate singleton header') Prevention
- Use headers[name] = value (replace) instead of headers.add() for singleton fields
- Audit proxies that may append duplicate User-Agent/Host/Content-Type
- Treat duplicate Content-Length as a security incident
When it happens
Trigger: A request containing two of any singleton header - e.g. two Host: lines, two Content-Length: lines, or two Content-Type: lines. Duplicate Content-Length is the classic request-smuggling vector; duplicate User-Agent often comes from proxies appending their own.
Common situations: Request-smuggling attacks (duplicate Content-Length), proxies or SDKs that append a second User-Agent/Host, naive clients that call headers.add() instead of assignment, or buggy gateways that duplicate Content-Type.
Related errors
- Transfer-Encoding can't be present with Content-Length
- Request has duplicate `chunked` Transfer-Encoding
- Missing 'Host' header in request.
- Method cannot contain non-token characters {method!r} (found
- Invalid Content-Length header: {content_length_hdr!r}
AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04).
Data as JSON: /data/errors/e2431647ff938fdb.json.
Report an issue: GitHub.