Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-asgiref for openSUSE:Factory checked in at 2026-02-17 16:37:51 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-asgiref (Old) and /work/SRC/openSUSE:Factory/.python-asgiref.new.1977 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-asgiref" Tue Feb 17 16:37:51 2026 rev:15 rq:1333411 version:3.11.1 Changes: -------- --- /work/SRC/openSUSE:Factory/python-asgiref/python-asgiref.changes 2025-12-11 18:32:17.678682791 +0100 +++ /work/SRC/openSUSE:Factory/.python-asgiref.new.1977/python-asgiref.changes 2026-02-17 16:39:09.946898811 +0100 @@ -1,0 +2,7 @@ +Mon Feb 16 17:10:33 UTC 2026 - Dirk Müller <[email protected]> + +- update to 3.11.1 (bsc#1257403, CVE-2025-14550): + * Fixed sync_to_async wrapping callables with attribute named context + * CVE-2025-14550: Fixed duplicate header handling in WsgiToAsgi. + +------------------------------------------------------------------- Old: ---- asgiref-3.11.0.tar.gz New: ---- asgiref-3.11.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-asgiref.spec ++++++ --- /var/tmp/diff_new_pack.kwN4rl/_old 2026-02-17 16:39:10.826935599 +0100 +++ /var/tmp/diff_new_pack.kwN4rl/_new 2026-02-17 16:39:10.826935599 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-asgiref # -# Copyright (c) 2025 SUSE LLC and contributors +# Copyright (c) 2026 SUSE LLC and contributors # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -18,7 +18,7 @@ %{?sle15_python_module_pythons} Name: python-asgiref -Version: 3.11.0 +Version: 3.11.1 Release: 0 Summary: ASGI specs, helper code, and adapters License: BSD-3-Clause ++++++ asgiref-3.11.0.tar.gz -> asgiref-3.11.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/asgiref-3.11.0/PKG-INFO new/asgiref-3.11.1/PKG-INFO --- old/asgiref-3.11.0/PKG-INFO 2025-11-19 16:32:07.114693600 +0100 +++ new/asgiref-3.11.1/PKG-INFO 2026-02-03 14:29:22.749848600 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: asgiref -Version: 3.11.0 +Version: 3.11.1 Summary: ASGI specs, helper code, and adapters Home-page: https://github.com/django/asgiref/ Author: Django Software Foundation diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/asgiref-3.11.0/asgiref/__init__.py new/asgiref-3.11.1/asgiref/__init__.py --- old/asgiref-3.11.0/asgiref/__init__.py 2025-11-19 16:31:06.000000000 +0100 +++ new/asgiref-3.11.1/asgiref/__init__.py 2026-02-03 14:27:52.000000000 +0100 @@ -1 +1 @@ -__version__ = "3.11.0" +__version__ = "3.11.1" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/asgiref-3.11.0/asgiref/sync.py new/asgiref-3.11.1/asgiref/sync.py --- old/asgiref-3.11.0/asgiref/sync.py 2025-11-19 16:29:14.000000000 +0100 +++ new/asgiref-3.11.1/asgiref/sync.py 2026-01-29 14:13:52.000000000 +0100 @@ -432,9 +432,11 @@ or iscoroutinefunction(getattr(func, "__call__", func)) ): raise TypeError("sync_to_async can only be applied to sync functions.") + + functools.update_wrapper(self, func) self.func = func self.context = context - functools.update_wrapper(self, func) + self._thread_sensitive = thread_sensitive markcoroutinefunction(self) if thread_sensitive and executor is not None: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/asgiref-3.11.0/asgiref/wsgi.py new/asgiref-3.11.1/asgiref/wsgi.py --- old/asgiref-3.11.0/asgiref/wsgi.py 2025-10-05 11:02:07.000000000 +0200 +++ new/asgiref-3.11.1/asgiref/wsgi.py 2026-02-03 14:27:10.000000000 +0100 @@ -1,4 +1,5 @@ import sys +from collections import defaultdict from tempfile import SpooledTemporaryFile from asgiref.sync import AsyncToSync, sync_to_async @@ -9,8 +10,9 @@ Wraps a WSGI application to make it into an ASGI application. """ - def __init__(self, wsgi_application): + def __init__(self, wsgi_application, duplicate_header_limit=100): self.wsgi_application = wsgi_application + self.duplicate_header_limit = duplicate_header_limit async def __call__(self, scope, receive, send): """ @@ -18,7 +20,9 @@ We return a new WsgiToAsgiInstance here with the WSGI app and the scope, ready to respond when it is __call__ed. """ - await WsgiToAsgiInstance(self.wsgi_application)(scope, receive, send) + await WsgiToAsgiInstance(self.wsgi_application, self.duplicate_header_limit)( + scope, receive, send + ) class WsgiToAsgiInstance: @@ -26,8 +30,9 @@ Per-socket instance of a wrapped WSGI application """ - def __init__(self, wsgi_application): + def __init__(self, wsgi_application, duplicate_header_limit=100): self.wsgi_application = wsgi_application + self.duplicate_header_limit = duplicate_header_limit self.response_started = False self.response_content_length = None @@ -84,6 +89,7 @@ environ["REMOTE_ADDR"] = scope["client"][0] # Go through headers and make them into environ entries + _headers = defaultdict(list) for name, value in self.scope.get("headers", []): name = name.decode("latin1") if name == "content-length": @@ -94,9 +100,17 @@ corrected_name = "HTTP_%s" % name.upper().replace("-", "_") # HTTPbis say only ASCII chars are allowed in headers, but we latin1 just in case value = value.decode("latin1") - if corrected_name in environ: - value = environ[corrected_name] + "," + value - environ[corrected_name] = value + if ( + self.duplicate_header_limit + and len(_headers[corrected_name]) >= self.duplicate_header_limit + ): + raise ValueError( + f"Too many duplicate headers: {corrected_name} exceeds limit of" + f"{self.duplicate_header_limit}" + ) + _headers[corrected_name].append(value) + for name, values in _headers.items(): + environ[name] = ",".join(values) return environ def start_response(self, status, response_headers, exc_info=None): @@ -138,7 +152,24 @@ this so that the start_response callable is called in the same thread. """ # Translate the scope and incoming request body into a WSGI environ - environ = self.build_environ(self.scope, body) + try: + environ = self.build_environ(self.scope, body) + except ValueError: + # Return 400 Bad Request if header limit exceeded + self.sync_send( + { + "type": "http.response.start", + "status": 400, + "headers": [(b"content-type", b"text/plain")], + } + ) + self.sync_send( + { + "type": "http.response.body", + "body": b"Bad Request: Too many duplicate headers", + } + ) + return # Run the WSGI app bytes_sent = 0 for output in self.wsgi_application(environ, self.start_response): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/asgiref-3.11.0/asgiref.egg-info/PKG-INFO new/asgiref-3.11.1/asgiref.egg-info/PKG-INFO --- old/asgiref-3.11.0/asgiref.egg-info/PKG-INFO 2025-11-19 16:32:07.000000000 +0100 +++ new/asgiref-3.11.1/asgiref.egg-info/PKG-INFO 2026-02-03 14:29:22.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: asgiref -Version: 3.11.0 +Version: 3.11.1 Summary: ASGI specs, helper code, and adapters Home-page: https://github.com/django/asgiref/ Author: Django Software Foundation diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/asgiref-3.11.0/tests/test_sync_contextvars.py new/asgiref-3.11.1/tests/test_sync_contextvars.py --- old/asgiref-3.11.0/tests/test_sync_contextvars.py 2025-11-19 16:29:14.000000000 +0100 +++ new/asgiref-3.11.1/tests/test_sync_contextvars.py 2026-01-29 14:13:52.000000000 +0100 @@ -138,3 +138,24 @@ sync_function = async_to_sync(async_function) assert sync_function() == 42 assert foo.get() == "baz" + + [email protected] +async def test_sync_to_async_contextvars_with_callable_with_context_attribute(): + """ + Tests that a callable object with a `context` attribute + can be wrapped with `sync_to_async` without overwriting the `context` attribute + and still returns the expected result. + """ + # Define sync Callable + class SyncCallable: + def __init__(self): + # Should not be copied to the SyncToAsync wrapper. + self.context = ... + + def __call__(self): + return 42 + + async_function = sync_to_async(SyncCallable()) + assert async_function.context is None + assert await async_function() == 42 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/asgiref-3.11.0/tests/test_wsgi.py new/asgiref-3.11.1/tests/test_wsgi.py --- old/asgiref-3.11.0/tests/test_wsgi.py 2024-12-07 09:15:18.000000000 +0100 +++ new/asgiref-3.11.1/tests/test_wsgi.py 2026-02-03 14:27:10.000000000 +0100 @@ -315,3 +315,68 @@ } assert (await instance.receive_output(1)) == {"type": "http.response.body"} + + [email protected] +async def test_duplicate_header_limit(): + def wsgi_application(environ, start_response): + start_response("200 OK", []) + return [b"OK"] + + application = WsgiToAsgi(wsgi_application, duplicate_header_limit=5) + instance = ApplicationCommunicator( + application, + { + "type": "http", + "http_version": "1.0", + "method": "GET", + "path": "/", + "query_string": b"", + "headers": [[b"x-test", b"value"] for _ in range(10)], + }, + ) + await instance.send_input({"type": "http.request"}) + + assert (await instance.receive_output(1)) == { + "type": "http.response.start", + "status": 400, + "headers": [(b"content-type", b"text/plain")], + } + assert (await instance.receive_output(1)) == { + "type": "http.response.body", + "body": b"Bad Request: Too many duplicate headers", + } + + [email protected] +async def test_duplicate_header_limit_disabled(): + def wsgi_application(environ, start_response): + assert "HTTP_X_TEST" in environ + start_response("200 OK", []) + return [b"OK"] + + application = WsgiToAsgi(wsgi_application, duplicate_header_limit=None) + instance = ApplicationCommunicator( + application, + { + "type": "http", + "http_version": "1.0", + "method": "GET", + "path": "/", + "query_string": b"", + "headers": [[b"x-test", b"value"] for _ in range(200)], + }, + ) + await instance.send_input({"type": "http.request"}) + + assert (await instance.receive_output(1)) == { + "type": "http.response.start", + "status": 200, + "headers": [], + } + assert (await instance.receive_output(1)) == { + "type": "http.response.body", + "body": b"OK", + "more_body": True, + } + assert (await instance.receive_output(1)) == {"type": "http.response.body"}
