fix(client): strip IPv6 link-local zone id from Host header - #13409
fix(client): strip IPv6 link-local zone id from Host header#13409zhaoxinyi02 wants to merge 1 commit into
Conversation
Per RFC 6874 §4, the zone id of an IPv6 link-local address only has local significance at the sending host and must be stripped from the outgoing Host header. aiohttp was sending it verbatim (e.g. Host: [fe80::1%eth0]), causing servers that strictly validate the Host header (nginx >= 1.29.4) to reject the request with 400 Bad Request. Strip the zone id before building the Host header in _update_headers, and also before building the CONNECT authority form URI in _send. Fixes #13401 Signed-off-by: 赵鑫亿 <98445030+zhaoxinyi02@users.noreply.github.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master aio-libs/aiohttp#13409 +/- ##
==========================================
- Coverage 98.99% 98.99% -0.01%
==========================================
Files 132 132
Lines 49454 49468 +14
Branches 2572 2574 +2
==========================================
+ Hits 48959 48971 +12
- Misses 371 372 +1
- Partials 124 125 +1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. |
Confidence Score: 5/5The PR appears safe to merge with no actionable correctness or security failures identified. The temporary URL rewrite removes the locally significant zone identifier only from transmitted authority values, preserves port handling, and leaves the original scoped URL intact for connection routing. Reviews (1): Last reviewed commit: "fix(client): strip IPv6 link-local zone ..." | Re-trigger Greptile |
Merging this PR will improve performance by 9.26%
|
| Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|
| ⚡ | test_simple_web_file_response[ssl-large] |
307.7 ms | 281.6 ms | +9.26% |
Tip
Curious why this is faster? Comment @codspeedbot explain why this is faster on this PR, or directly use the CodSpeed MCP with your agent.
Comparing zhaoxinyi02:fix/13401-strip-ipv6-zone-id (3d3d9e5) with master (d041d4d)2
Footnotes
-
83 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩
-
No successful run was found on
master(77c5108) during the generation of this report, so d041d4d was used instead as the comparison base. There might be some changes unrelated to this pull request in this report. ↩
Problem
When a request is made to a URL containing an IPv6 link-local address with a zone id (e.g.
http://[fe80::1%eth0]/), aiohttp sends the zone id verbatim as part of the Host header:Host: [fe80::1%eth0].Per RFC 6874 §4, the zone id only has local significance to the sending host and must be removed by the client:
Servers that validate the Host header strictly against RFC 3986 — for example nginx from 1.29.4 onward — now reject such requests with
400 Bad Request.Root cause
_update_headersbuilds the Host header fromself.url.host_port_subcomponent, which yarl returns including the zone id. The same applies to the CONNECT authority form URI built in_send.Fix
Strip the zone id before building the Host header in
_update_headers, and before building the CONNECT authority form URI in_send. Both useurl.with_host(raw_host.split("%", 1)[0])when the raw host contains%.raw_hostis used (rather thanhost) so the check operates on the un-decoded form, matching yarl's own IPv6 handling.Testing
Added two regression tests to
tests/test_client_request.py:test_host_header_ipv6_link_local_zone_id_stripped— verifiesHost: [fe80::1](zone id removed, no port)test_host_header_ipv6_link_local_zone_id_with_port— verifiesHost: [fe80::1]:99(zone id removed, port preserved)Existing tests for plain IPv6, IPv4, domains, and explicit Host headers continue to pass, confirming the fix only affects the zone-id case.
Fixes aio-libs/yarl#1862