Medium severity5.3NVD Advisory· Published Apr 1, 2026· Updated Apr 16, 2026
CVE-2026-34519
CVE-2026-34519
Description
AIOHTTP is an asynchronous HTTP client/server framework for asyncio and Python. Prior to version 3.13.4, an attacker who controls the reason parameter when creating a Response may be able to inject extra headers or similar exploits. This issue has been patched in version 3.13.4.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
aiohttpPyPI | < 3.13.4 | 3.13.4 |
Affected products
1Patches
153b35a2f8869Restrict reason (#12209) (#12212)
6 files changed · +43 −8
aiohttp/http_writer.py+1 −0 modified@@ -361,6 +361,7 @@ def _safe_header(string: str) -> str: def _py_serialize_headers(status_line: str, headers: "CIMultiDict[str]") -> bytes: + _safe_header(status_line) headers_gen = (_safe_header(k) + ": " + _safe_header(v) for k, v in headers.items()) line = status_line + "\r\n" + "\r\n".join(headers_gen) + "\r\n\r\n" return line.encode("utf-8")
aiohttp/_http_writer.pyx+1 −1 modified@@ -131,7 +131,7 @@ def _serialize_headers(str status_line, headers): _init_writer(&writer, buf) try: - if _write_str(&writer, status_line) < 0: + if _write_str_raise_on_nlcr(&writer, status_line) < 0: raise if _write_byte(&writer, b'\r') < 0: raise
aiohttp/web_exceptions.py+2 −0 modified@@ -101,6 +101,8 @@ def __init__( "body argument is deprecated for http web exceptions", DeprecationWarning, ) + if reason is not None and ("\r" in reason or "\n" in reason): + raise ValueError("Reason cannot contain \\r or \\n") Response.__init__( self, status=self.status_code,
aiohttp/web_response.py+2 −2 modified@@ -158,8 +158,8 @@ def _set_status(self, status: int, reason: Optional[str]) -> None: self._status = int(status) if reason is None: reason = REASON_PHRASES.get(self._status, "") - elif "\n" in reason: - raise ValueError("Reason cannot contain \\n") + elif "\r" in reason or "\n" in reason: + raise ValueError("Reason cannot contain \\r or \\n") self._reason = reason @property
tests/test_web_exceptions.py+11 −1 modified@@ -273,5 +273,15 @@ def test_unicode_text_body_unauthorized() -> None: def test_multiline_reason() -> None: - with pytest.raises(ValueError, match=r"Reason cannot contain \\n"): + with pytest.raises(ValueError, match=r"Reason cannot contain"): web.HTTPOk(reason="Bad\r\nInjected-header: foo") + + +def test_reason_with_cr() -> None: + with pytest.raises(ValueError, match=r"Reason cannot contain"): + web.HTTPOk(reason="OK\rSet-Cookie: evil=1") + + +def test_reason_with_lf() -> None: + with pytest.raises(ValueError, match=r"Reason cannot contain"): + web.HTTPOk(reason="OK\nSet-Cookie: evil=1")
tests/test_web_response.py+26 −4 modified@@ -13,7 +13,8 @@ from multidict import CIMultiDict, CIMultiDictProxy, MultiDict from re_assert import Matches -from aiohttp import HttpVersion, HttpVersion10, HttpVersion11, hdrs +from aiohttp import HttpVersion, HttpVersion10, HttpVersion11, hdrs, web +from aiohttp.abc import AbstractStreamWriter from aiohttp.helpers import ETag from aiohttp.http_writer import StreamWriter, _serialize_headers from aiohttp.multipart import BodyPartReader, MultipartWriter @@ -1008,6 +1009,27 @@ def test_set_status_with_empty_reason() -> None: assert resp.reason == "" +def test_set_status_reason_with_cr() -> None: + resp = web.StreamResponse() + + with pytest.raises(ValueError, match="Reason cannot contain"): + resp.set_status(200, "OK\rSet-Cookie: evil=1") + + +def test_set_status_reason_with_lf() -> None: + resp = web.StreamResponse() + + with pytest.raises(ValueError, match="Reason cannot contain"): + resp.set_status(200, "OK\nSet-Cookie: evil=1") + + +def test_set_status_reason_with_crlf() -> None: + resp = web.StreamResponse() + + with pytest.raises(ValueError, match="Reason cannot contain"): + resp.set_status(200, "OK\r\nSet-Cookie: evil=1") + + async def test_start_force_close() -> None: req = make_request("GET", "/") resp = StreamResponse() @@ -1308,9 +1330,9 @@ async def test_render_with_body(buf, writer) -> None: ) -async def test_multiline_reason(buf, writer) -> None: - with pytest.raises(ValueError, match=r"Reason cannot contain \\n"): - Response(reason="Bad\r\nInjected-header: foo") +async def test_multiline_reason(buf: bytearray, writer: AbstractStreamWriter) -> None: + with pytest.raises(ValueError, match=r"Reason cannot contain \\r or \\n"): + web.Response(reason="Bad\r\nInjected-header: foo") async def test_send_set_cookie_header(buf, writer) -> None:
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/aio-libs/aiohttp/commit/53b35a2f8869c37a133e60bf1a82a1c01642ba2bnvdPatchWEB
- github.com/aio-libs/aiohttp/security/advisories/GHSA-mwh4-6h8g-pg8wnvdPatchVendor AdvisoryWEB
- github.com/advisories/GHSA-mwh4-6h8g-pg8wghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-34519ghsaADVISORY
- github.com/aio-libs/aiohttp/releases/tag/v3.13.4nvdRelease NotesWEB
News mentions
0No linked articles in our index yet.