Your message dated Sat, 11 Jul 2026 10:32:48 +0000
with message-id <[email protected]>
and subject line Released in 13.6
has caused the Debian Bug report #1141383,
regarding trixie-pu: package python-daphne/4.1.2-2+deb13u1
to be marked as done.

This means that you claim that the problem has been dealt with.
If this is not the case it is now your responsibility to reopen the
Bug report if necessary, and/or fix the problem forthwith.

(NB: If you are a system administrator and have no idea what this
message is talking about, this may indicate a serious mail system
misconfiguration somewhere. Please contact [email protected]
immediately.)


-- 
1141383: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1141383
Debian Bug Tracking System
Contact [email protected] with problems
--- Begin Message ---
Package: release.debian.org
Severity: normal
Tags: trixie
X-Debbugs-Cc: [email protected], [email protected]
Control: affects -1 + src:python-daphne
User: [email protected]
Usertags: pu

  * CVE-2026-44545: DoS via unbounded WebSocket message sizes
  * CVE-2026-44546: Header injection on WebSocket upgrade path
  * (Closes: #1138864)
diffstat for python-daphne-4.1.2 python-daphne-4.1.2

 changelog                                                               |   10 
 patches/0001-Fixed-CVE-2026-44546-Prevent-header-injection-on-Web.patch |  154 
+++++++
 patches/0002-Fixed-CVE-2026-44545-Limit-WebSocket-sizes-in-autoba.patch |  212 
++++++++++
 patches/series                                                          |    2 
 4 files changed, 378 insertions(+)

diff -Nru python-daphne-4.1.2/debian/changelog 
python-daphne-4.1.2/debian/changelog
--- python-daphne-4.1.2/debian/changelog        2024-08-18 20:46:43.000000000 
+0300
+++ python-daphne-4.1.2/debian/changelog        2026-07-03 20:38:15.000000000 
+0300
@@ -1,3 +1,13 @@
+python-daphne (4.1.2-2+deb13u1) trixie; urgency=medium
+
+  * Non-maintainer upload.
+  * CVE-2026-44545: DoS via unbounded WebSocket message sizes
+  * CVE-2026-44546: Header injection on WebSocket upgrade path
+  * (Closes: #1138864)
+
+
+ -- Adrian Bunk <[email protected]>  Fri, 03 Jul 2026 20:38:15 +0300
+
 python-daphne (4.1.2-2) unstable; urgency=medium
 
   * Team upload.
diff -Nru 
python-daphne-4.1.2/debian/patches/0001-Fixed-CVE-2026-44546-Prevent-header-injection-on-Web.patch
 
python-daphne-4.1.2/debian/patches/0001-Fixed-CVE-2026-44546-Prevent-header-injection-on-Web.patch
--- 
python-daphne-4.1.2/debian/patches/0001-Fixed-CVE-2026-44546-Prevent-header-injection-on-Web.patch
  1970-01-01 02:00:00.000000000 +0200
+++ 
python-daphne-4.1.2/debian/patches/0001-Fixed-CVE-2026-44546-Prevent-header-injection-on-Web.patch
  2026-07-03 20:36:46.000000000 +0300
@@ -0,0 +1,154 @@
+From c69f1a75efa90c423f1a93f329fd8d6814e221a3 Mon Sep 17 00:00:00 2001
+From: Carlton Gibson <[email protected]>
+Date: Wed, 6 May 2026 09:23:37 +0200
+Subject: Fixed CVE-2026-44546: Prevent header injection on WebSocket upgrade
+ path
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Fixed a header injection vulnerability on the WebSocket upgrade path
+(CVE-2026-44546).
+
+Header values containing ``\x0b``, ``\x0c``, ``\x1c``, ``\x1d``, ``\x1e``, or
+``\x85`` were parsed as a single header by Twisted but split into multiple
+headers by autobahn during the WebSocket handshake. An attacker could exploit
+this parser differential to smuggle additional headers (e.g. authentication
+tokens, ``X-Forwarded-For``, ``Origin``, ``Daphne-Root-Path``) into the ASGI
+scope passed to the application.
+
+Daphne now rejects requests carrying these bytes in any header value with a 400
+Bad Request response, as required by RFC 9110 §5.5.
+
+Thanks to Rene Henningsen for the report.
+---
+ daphne/http_protocol.py | 12 ++++++--
+ daphne/utils.py         |  3 ++
+ tests/test_websocket.py | 64 +++++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 76 insertions(+), 3 deletions(-)
+
+diff --git a/daphne/http_protocol.py b/daphne/http_protocol.py
+index e9eba96..1319f46 100755
+--- a/daphne/http_protocol.py
++++ b/daphne/http_protocol.py
+@@ -9,7 +9,7 @@ from twisted.protocols.policies import ProtocolWrapper
+ from twisted.web import http
+ from zope.interface import implementer
+ 
+-from .utils import HEADER_NAME_RE, parse_x_forwarded_for
++from .utils import HEADER_NAME_RE, INVALID_HEADER_VALUE_BYTES, 
parse_x_forwarded_for
+ 
+ logger = logging.getLogger(__name__)
+ 
+@@ -70,11 +70,17 @@ class WebRequest(http.Request):
+         try:
+             self.request_start = time.time()
+ 
+-            # Validate header names.
+-            for name, _ in self.requestHeaders.getAllRawHeaders():
++            # Validate header names and values.
++            for name, values in self.requestHeaders.getAllRawHeaders():
+                 if not HEADER_NAME_RE.fullmatch(name):
+                     self.basic_error(400, b"Bad Request", "Invalid header 
name")
+                     return
++                for value in values:
++                    if INVALID_HEADER_VALUE_BYTES.intersection(value):
++                        self.basic_error(
++                            400, b"Bad Request", "Invalid header value"
++                        )
++                        return
+ 
+             # Get upgrade header
+             upgrade_header = None
+diff --git a/daphne/utils.py b/daphne/utils.py
+index 0699314..b634cd0 100644
+--- a/daphne/utils.py
++++ b/daphne/utils.py
+@@ -7,6 +7,9 @@ from twisted.web.http_headers import Headers
+ # 
https://github.com/python-hyper/h11/blob/a2c68948accadc3876dffcf979d98002e4a4ed27/h11/_abnf.py#L10-L21
+ HEADER_NAME_RE = re.compile(rb"[-!#$%&'*+.^_`|~0-9a-zA-Z]+")
+ 
++# Disallowed in field-value per RFC 9110 §5.5.
++INVALID_HEADER_VALUE_BYTES = frozenset(b"\x0b\x0c\x1c\x1d\x1e\x85")
++
+ 
+ def import_by_path(path):
+     """
+diff --git a/tests/test_websocket.py b/tests/test_websocket.py
+index 851143c..3ffbdb8 100644
+--- a/tests/test_websocket.py
++++ b/tests/test_websocket.py
+@@ -306,6 +306,70 @@ class TestWebsocket(DaphneTestCase):
+                 )
+ 
+ 
++class TestHeaderValueInjection(DaphneTestCase):
++    """
++    Twisted's bytes HTTP parser does not treat \\x0b, \\x0c, \\x1c, \\x1d, 
\\x1e
++    or \\x85 as line separators, but autobahn's WebSocket handshake parser
++    decodes to str and calls splitlines(), which does. Without rejection at
++    the Daphne edge, an attacker can smuggle additional headers into the
++    WebSocket ASGI scope through a single header value. Reject these bytes
++    on both paths so values can never reach a downstream str-based parser.
++    """
++
++    INVALID_BYTES = (
++        b"\x0b",  # vertical tab
++        b"\x0c",  # form feed
++        b"\x1c",  # file separator
++        b"\x1d",  # group separator
++        b"\x1e",  # record separator
++        b"\x85",  # NEL
++    )
++
++    def _websocket_upgrade_request(self, value):
++        return (
++            b"GET /ws HTTP/1.1\r\n"
++            b"Host: example.com\r\n"
++            b"Upgrade: websocket\r\n"
++            b"Connection: Upgrade\r\n"
++            b"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
++            b"Sec-WebSocket-Version: 13\r\n"
++            b"X-Padding: " + value + b"\r\n"
++            b"\r\n"
++        )
++
++    def _http_request(self, value):
++        return (
++            b"GET / HTTP/1.1\r\n"
++            b"Host: example.com\r\n"
++            b"X-Padding: " + value + b"\r\n"
++            b"\r\n"
++        )
++
++    def test_websocket_upgrade_rejects_smuggled_headers(self):
++        for byte in self.INVALID_BYTES:
++            with self.subTest(byte=byte):
++                value = b"innocent" + byte + b"X-Secret-Auth: admin-token"
++                response = self.run_daphne_raw(
++                    self._websocket_upgrade_request(value)
++                )
++                self.assertTrue(
++                    response.startswith(b"HTTP/1.1 400"),
++                    f"expected 400 for byte {byte!r}, got {response[:80]!r}",
++                )
++                # Confirm the smuggled header didn't slip past validation.
++                self.assertNotIn(b"X-Secret-Auth", response)
++
++    def test_http_request_rejects_invalid_header_value_bytes(self):
++        for byte in self.INVALID_BYTES:
++            with self.subTest(byte=byte):
++                value = b"innocent" + byte + b"injected"
++                response = self.run_daphne_raw(self._http_request(value))
++                self.assertTrue(
++                    response.startswith(b"HTTP/1.1 400"),
++                    f"expected 400 for byte {byte!r}, got {response[:80]!r}",
++                )
++
++
+ async def cancelling_application(scope, receive, send):
+     import asyncio
+ 
+-- 
+2.47.3
+
diff -Nru 
python-daphne-4.1.2/debian/patches/0002-Fixed-CVE-2026-44545-Limit-WebSocket-sizes-in-autoba.patch
 
python-daphne-4.1.2/debian/patches/0002-Fixed-CVE-2026-44545-Limit-WebSocket-sizes-in-autoba.patch
--- 
python-daphne-4.1.2/debian/patches/0002-Fixed-CVE-2026-44545-Limit-WebSocket-sizes-in-autoba.patch
  1970-01-01 02:00:00.000000000 +0200
+++ 
python-daphne-4.1.2/debian/patches/0002-Fixed-CVE-2026-44545-Limit-WebSocket-sizes-in-autoba.patch
  2026-07-03 20:36:46.000000000 +0300
@@ -0,0 +1,212 @@
+From 23e39f0c82ab8e7cb08ca7685a86af6dbd009097 Mon Sep 17 00:00:00 2001
+From: Carlton Gibson <[email protected]>
+Date: Wed, 6 May 2026 08:50:22 +0200
+Subject: Fixed CVE-2026-44545: Limit WebSocket sizes in autobahn config.
+
+Fixed a denial of service vulnerability via unbounded WebSocket message sizes.
+Daphne previously passed no message or frame size limits to autobahn, whose
+defaults are unbounded. This allowed an unauthenticated client to exhaust
+server memory by sending a very large WebSocket messages/frames
+(CVE-2026-44545).
+
+Both limits now default to 1 MiB and can be configured via the new
+``--websocket-max-message-size`` and ``--websocket-max-frame-size`` CLI flags
+(or the matching ``Server`` constructor arguments). Pass ``0`` to restore the
+previous unlimited behaviour.
+
+Thanks to ParkHyunWoo for the report.
+---
+ daphne/cli.py           | 18 +++++++++++
+ daphne/server.py        |  6 ++++
+ daphne/testing.py       | 15 ++++++++-
+ tests/test_websocket.py | 69 +++++++++++++++++++++++++++++++++++++++++
+ 4 files changed, 107 insertions(+), 1 deletion(-)
+
+diff --git a/daphne/cli.py b/daphne/cli.py
+index a036821..0c88a9c 100755
+--- a/daphne/cli.py
++++ b/daphne/cli.py
+@@ -107,6 +107,22 @@ class CommandLineInterface:
+             help="The number of seconds before a WebSocket is closed if no 
response to a keepalive ping",
+             default=30,
+         )
++        self.parser.add_argument(
++            "--websocket-max-message-size",
++            type=int,
++            help="Maximum size, in bytes, of an incoming WebSocket message. "
++            "0 disables the limit (not recommended; allows unauthenticated "
++            "memory exhaustion).",
++            default=1024 * 1024,
++        )
++        self.parser.add_argument(
++            "--websocket-max-frame-size",
++            type=int,
++            help="Maximum size, in bytes, of a single incoming WebSocket 
frame. "
++            "0 disables the limit (not recommended; allows unauthenticated "
++            "memory exhaustion).",
++            default=1024 * 1024,
++        )
+         self.parser.add_argument(
+             "--application-close-timeout",
+             type=int,
+@@ -269,6 +285,8 @@ class CommandLineInterface:
+             websocket_timeout=args.websocket_timeout,
+             websocket_connect_timeout=args.websocket_connect_timeout,
+             websocket_handshake_timeout=args.websocket_connect_timeout,
++            websocket_max_message_size=args.websocket_max_message_size,
++            websocket_max_frame_size=args.websocket_max_frame_size,
+             application_close_timeout=args.application_close_timeout,
+             action_logger=(
+                 AccessLogGenerator(access_log_stream) if access_log_stream 
else None
+diff --git a/daphne/server.py b/daphne/server.py
+index a6d3819..4225046 100755
+--- a/daphne/server.py
++++ b/daphne/server.py
+@@ -63,6 +63,8 @@ class Server:
+         proxy_forwarded_proto_header=None,
+         verbosity=1,
+         websocket_handshake_timeout=5,
++        websocket_max_message_size=1024 * 1024,
++        websocket_max_frame_size=1024 * 1024,
+         application_close_timeout=10,
+         ready_callable=None,
+         server_name="daphne",
+@@ -83,6 +85,8 @@ class Server:
+         self.websocket_timeout = websocket_timeout
+         self.websocket_connect_timeout = websocket_connect_timeout
+         self.websocket_handshake_timeout = websocket_handshake_timeout
++        self.websocket_max_message_size = websocket_max_message_size
++        self.websocket_max_frame_size = websocket_max_frame_size
+         self.application_close_timeout = application_close_timeout
+         self.root_path = root_path
+         self.verbosity = verbosity
+@@ -104,6 +108,8 @@ class Server:
+             autoPingTimeout=self.ping_timeout,
+             allowNullOrigin=True,
+             openHandshakeTimeout=self.websocket_handshake_timeout,
++            maxMessagePayloadSize=self.websocket_max_message_size,
++            maxFramePayloadSize=self.websocket_max_frame_size,
+         )
+         if self.verbosity <= 1:
+             # Redirect the Twisted log to nowhere
+diff --git a/daphne/testing.py b/daphne/testing.py
+index 785edf9..6a426c8 100644
+--- a/daphne/testing.py
++++ b/daphne/testing.py
+@@ -18,12 +18,21 @@ class BaseDaphneTestingInstance:
+     startup_timeout = 2
+ 
+     def __init__(
+-        self, xff=False, http_timeout=None, request_buffer_size=None, *, 
application
++        self,
++        xff=False,
++        http_timeout=None,
++        request_buffer_size=None,
++        websocket_max_message_size=None,
++        websocket_max_frame_size=None,
++        *,
++        application,
+     ):
+         self.xff = xff
+         self.http_timeout = http_timeout
+         self.host = "127.0.0.1"
+         self.request_buffer_size = request_buffer_size
++        self.websocket_max_message_size = websocket_max_message_size
++        self.websocket_max_frame_size = websocket_max_frame_size
+         self.application = application
+ 
+     def get_application(self):
+@@ -41,6 +50,10 @@ class BaseDaphneTestingInstance:
+             kwargs["proxy_forwarded_proto_header"] = "X-Forwarded-Proto"
+         if self.http_timeout:
+             kwargs["http_timeout"] = self.http_timeout
++        if self.websocket_max_message_size is not None:
++            kwargs["websocket_max_message_size"] = 
self.websocket_max_message_size
++        if self.websocket_max_frame_size is not None:
++            kwargs["websocket_max_frame_size"] = self.websocket_max_frame_size
+         # Start up process
+         self.process = DaphneProcess(
+             host=self.host,
+diff --git a/tests/test_websocket.py b/tests/test_websocket.py
+index 3ffbdb8..844b14e 100644
+--- a/tests/test_websocket.py
++++ b/tests/test_websocket.py
+@@ -267,6 +267,75 @@ class TestWebsocket(DaphneTestCase):
+                 "bytes": b"what is here? \xe2",
+             }
+ 
++    def assert_oversized_frame_rejected(self, test_app):
++        """
++        Sends a 16-byte text frame and asserts the application sees only
++        connect + disconnect — i.e. autobahn dropped the connection (its
++        default failByDrop behaviour) before dispatching the payload.
++        """
++        test_app.add_send_messages([{"type": "websocket.accept"}])
++        sock, _ = self.websocket_handshake(test_app)
++        _, messages = test_app.get_received()
++        self.assert_valid_websocket_connect_message(messages[0])
++        self.websocket_send_frame(sock, "x" * 16)
++        deadline = time.time() + 2
++        final_messages = []
++        while time.time() < deadline:
++            _, final_messages = test_app.get_received()
++            if any(m["type"] == "websocket.disconnect" for m in 
final_messages):
++                break
++            time.sleep(0.05)
++        try:
++            sock.close()
++        except OSError:
++            pass
++        types = [m["type"] for m in final_messages]
++        self.assertEqual(
++            types,
++            ["websocket.connect", "websocket.disconnect"],
++            "Oversized frame should not have been delivered to the "
++            f"application, but got: {types}",
++        )
++
++    def test_websocket_max_message_size(self):
++        """
++        Tests that an incoming WebSocket message exceeding
++        ``websocket_max_message_size`` is rejected by autobahn before it
++        reaches the application.
++        """
++        # 16-byte frame > 8-byte message limit.
++        with DaphneTestingInstance(websocket_max_message_size=8) as test_app:
++            self.assert_oversized_frame_rejected(test_app)
++
++    def test_websocket_max_frame_size(self):
++        """
++        Tests that an incoming WebSocket frame exceeding
++        ``websocket_max_frame_size`` is rejected by autobahn before it
++        reaches the application, independently of the message size limit.
++        """
++        # Large message limit, so the frame size limit is what trips.
++        with DaphneTestingInstance(
++            websocket_max_frame_size=8,
++            websocket_max_message_size=1024 * 1024,
++        ) as test_app:
++            self.assert_oversized_frame_rejected(test_app)
++
++    def test_websocket_max_message_size_allows_under_limit(self):
++        """
++        Tests that messages under ``websocket_max_message_size`` are
++        delivered to the application unchanged.
++        """
++        with DaphneTestingInstance(websocket_max_message_size=64) as test_app:
++            test_app.add_send_messages([{"type": "websocket.accept"}])
++            sock, _ = self.websocket_handshake(test_app)
++            _, messages = test_app.get_received()
++            self.assert_valid_websocket_connect_message(messages[0])
++            test_app.add_send_messages(
++                [{"type": "websocket.send", "text": "ack"}]
++            )
++            self.websocket_send_frame(sock, "x" * 16)
++            assert self.websocket_receive_frame(sock) == "ack"
++
+     def test_http_timeout(self):
+         """
+         Tests that the HTTP timeout doesn't kick in for WebSockets
+-- 
+2.47.3
+
diff -Nru python-daphne-4.1.2/debian/patches/series 
python-daphne-4.1.2/debian/patches/series
--- python-daphne-4.1.2/debian/patches/series   2024-08-18 20:46:43.000000000 
+0300
+++ python-daphne-4.1.2/debian/patches/series   2026-07-03 20:38:13.000000000 
+0300
@@ -1 +1,3 @@
 twisted-24.7.0.patch
+0001-Fixed-CVE-2026-44546-Prevent-header-injection-on-Web.patch
+0002-Fixed-CVE-2026-44545-Limit-WebSocket-sizes-in-autoba.patch

--- End Message ---
--- Begin Message ---
Version: 13.6

This update was released as part of 13.6.

--- End Message ---

Reply via email to