Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python310 for openSUSE:Factory 
checked in at 2026-02-13 12:46:26
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python310 (Old)
 and      /work/SRC/openSUSE:Factory/.python310.new.1977 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python310"

Fri Feb 13 12:46:26 2026 rev:71 rq:1332783 version:3.10.19

Changes:
--------
--- /work/SRC/openSUSE:Factory/python310/python310.changes      2025-12-22 
22:52:44.765692477 +0100
+++ /work/SRC/openSUSE:Factory/.python310.new.1977/python310.changes    
2026-02-13 12:46:42.358204311 +0100
@@ -1,0 +2,23 @@
+Wed Feb 11 23:49:49 CET 2026 - Matej Cepl <[email protected]>
+
+- CVE-2025-11468: preserving parens when folding comments in
+  email headers (bsc#1257029, gh#python/cpython#143935).
+  CVE-2025-11468-email-hdr-fold-comment.patch
+- CVE-2026-0672: rejects control characters in http cookies.
+  (bsc#1257031, gh#python/cpython#143919)
+  CVE-2026-0672-http-hdr-inject-cookie-Morsel.patch
+- CVE-2026-0865: rejecting control characters in
+  wsgiref.headers.Headers, which could be abused for injecting
+  false HTTP headers. (bsc#1257042, gh#python/cpython#143916)
+  CVE-2026-0865-wsgiref-ctrl-chars.patch
+- CVE-2025-15366: basically the same as the previous patch for
+  IMAP protocol. (bsc#1257044, gh#python/cpython#143921)
+  CVE-2025-15366-imap-ctrl-chars.patch
+- CVE-2025-15282: basically the same as the previous patch for
+  urllib library. (bsc#1257046, gh#python/cpython#143925)
+  CVE-2025-15282-urllib-ctrl-chars.patch
+- CVE-2025-15367: basically the same as the previous patch for
+  poplib library. (bsc#1257041, gh#python/cpython#143923)
+  CVE-2025-15367-poplib-ctrl-chars.patch
+
+-------------------------------------------------------------------

New:
----
  CVE-2025-11468-email-hdr-fold-comment.patch
  CVE-2025-15282-urllib-ctrl-chars.patch
  CVE-2025-15366-imap-ctrl-chars.patch
  CVE-2025-15367-poplib-ctrl-chars.patch
  CVE-2026-0672-http-hdr-inject-cookie-Morsel.patch
  CVE-2026-0865-wsgiref-ctrl-chars.patch

----------(New B)----------
  New:  email headers (bsc#1257029, gh#python/cpython#143935).
  CVE-2025-11468-email-hdr-fold-comment.patch
- CVE-2026-0672: rejects control characters in http cookies.
  New:  urllib library. (bsc#1257046, gh#python/cpython#143925)
  CVE-2025-15282-urllib-ctrl-chars.patch
- CVE-2025-15367: basically the same as the previous patch for
  New:  IMAP protocol. (bsc#1257044, gh#python/cpython#143921)
  CVE-2025-15366-imap-ctrl-chars.patch
- CVE-2025-15282: basically the same as the previous patch for
  New:  poplib library. (bsc#1257041, gh#python/cpython#143923)
  CVE-2025-15367-poplib-ctrl-chars.patch
  New:  (bsc#1257031, gh#python/cpython#143919)
  CVE-2026-0672-http-hdr-inject-cookie-Morsel.patch
- CVE-2026-0865: rejecting control characters in
  New:  false HTTP headers. (bsc#1257042, gh#python/cpython#143916)
  CVE-2026-0865-wsgiref-ctrl-chars.patch
- CVE-2025-15366: basically the same as the previous patch for
----------(New E)----------

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python310.spec ++++++
--- /var/tmp/diff_new_pack.fl5Wb2/_old  2026-02-13 12:46:44.530295281 +0100
+++ /var/tmp/diff_new_pack.fl5Wb2/_new  2026-02-13 12:46:44.534295448 +0100
@@ -160,7 +160,6 @@
 # PATCH-FIX-SLE no-skipif-doctests.patch jsc#SLE-13738 [email protected]
 # SLE-15 version of Sphinx doesn't know about skipif directive in doctests.
 Patch11:        no-skipif-doctests.patch
-
 # PATCH-FIX-SLE skip-test_pyobject_freed_is_freed.patch [email protected]
 # skip a test failing on SLE-15
 Patch15:        skip-test_pyobject_freed_is_freed.patch
@@ -216,6 +215,25 @@
 # PATCH-FIX-UPSTREAM CVE-2025-13837-plistlib-mailicious-length.patch 
bsc#1254401 [email protected]
 # protect against OOM when loading malicious content
 Patch33:        CVE-2025-13837-plistlib-mailicious-length.patch 
+# PATCH-FIX-UPSTREAM CVE-2025-11468-email-hdr-fold-comment.patch bsc#1257029 
[email protected]
+# this patch makes things totally awesome
+Patch34:        CVE-2025-11468-email-hdr-fold-comment.patch
+# PATCH-FIX-UPSTREAM CVE-2026-0672-http-hdr-inject-cookie-Morsel.patch 
bsc#1257031 [email protected]
+# rejects control characters in http cookies.
+Patch35:        CVE-2026-0672-http-hdr-inject-cookie-Morsel.patch
+# PATCH-FIX-UPSTREAM CVE-2026-0865-wsgiref-ctrl-chars.patch bsc#1257042 
[email protected]
+# Reject control characters in wsgiref.headers.Headers
+Patch37:        CVE-2026-0865-wsgiref-ctrl-chars.patch
+# PATCH-FIX-UPSTREAM CVE-2025-15366-imap-ctrl-chars.patch bsc#1257044 
[email protected]
+# Reject control characters in wsgiref.headers.Headers
+Patch38:        CVE-2025-15366-imap-ctrl-chars.patch
+# PATCH-FIX-UPSTREAM CVE-2025-15282-urllib-ctrl-chars.patch bsc#1257046 
[email protected]
+# Reject control characters in urllib
+Patch39:        CVE-2025-15282-urllib-ctrl-chars.patch
+# PATCH-FIX-UPSTREAM CVE-2025-15367-poplib-ctrl-chars.patch bsc#1257041 
[email protected]
+# Reject control characters in poplib
+Patch40:        CVE-2025-15367-poplib-ctrl-chars.patch
+### END OF PATCHES                                                             
 
 BuildRequires:  autoconf-archive
 BuildRequires:  automake
 BuildRequires:  fdupes

++++++ CVE-2025-11468-email-hdr-fold-comment.patch ++++++
>From 2065aa5b8f2bcdea2f628686c57974793a62c42b Mon Sep 17 00:00:00 2001
From: Seth Michael Larson <[email protected]>
Date: Mon, 19 Jan 2026 06:38:22 -0600
Subject: [PATCH] [3.10] gh-143935: Email preserve parens when folding comments
 (GH-143936)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Fix a bug in the folding of comments when flattening an email message
using a modern email policy. Comments consisting of a very long sequence of
non-foldable characters could trigger a forced line wrap that omitted the
required leading space on the continuation line, causing the remainder of
the comment to be interpreted as a new header field. This enabled header
injection with carefully crafted inputs.
(cherry picked from commit 17d1490)

Co-authored-by: Seth Michael Larson [email protected]
Co-authored-by: Denis Ledoux [email protected]

- Issue: Fix folding of long comments of unfoldable characters in email headers 
#143935

Signed-off-by: Edgar Ramírez Mondragón <[email protected]>
---
 Lib/email/_header_value_parser.py             | 15 +++++++++++-
 .../test_email/test__header_value_parser.py   | 23 +++++++++++++++++++
 ...-01-16-14-40-31.gh-issue-143935.U2YtKl.rst |  6 +++++
 3 files changed, 43 insertions(+), 1 deletion(-)
 create mode 100644 
Misc/NEWS.d/next/Security/2026-01-16-14-40-31.gh-issue-143935.U2YtKl.rst

diff --git a/Lib/email/_header_value_parser.py 
b/Lib/email/_header_value_parser.py
index dbc0bd8196af52..2c05abeadea22b 100644
--- a/Lib/email/_header_value_parser.py
+++ b/Lib/email/_header_value_parser.py
@@ -101,6 +101,12 @@ def make_quoted_pairs(value):
     return str(value).replace('\\', '\\\\').replace('"', '\\"')
 
 
+def make_parenthesis_pairs(value):
+    """Escape parenthesis and backslash for use within a comment."""
+    return str(value).replace('\\', '\\\\') \
+        .replace('(', '\\(').replace(')', '\\)')
+
+
 def quote_string(value):
     escaped = make_quoted_pairs(value)
     return f'"{escaped}"'
@@ -927,7 +933,7 @@ def value(self):
         return ' '
 
     def startswith_fws(self):
-        return True
+        return self and self[0] in WSP
 
 
 class ValueTerminal(Terminal):
@@ -2865,6 +2871,13 @@ def _refold_parse_tree(parse_tree, *, policy):
                     [ValueTerminal(make_quoted_pairs(p), 'ptext')
                      for p in newparts] +
                     [ValueTerminal('"', 'ptext')])
+            if part.token_type == 'comment':
+                newparts = (
+                    [ValueTerminal('(', 'ptext')] +
+                    [ValueTerminal(make_parenthesis_pairs(p), 'ptext')
+                     if p.token_type == 'ptext' else p
+                     for p in newparts] +
+                    [ValueTerminal(')', 'ptext')])
             if not part.as_ew_allowed:
                 wrap_as_ew_blocked += 1
                 newparts.append(end_ew_not_allowed)
diff --git a/Lib/test/test_email/test__header_value_parser.py 
b/Lib/test/test_email/test__header_value_parser.py
index 6a4ecafd68b4ab..2eaaaaef675284 100644
--- a/Lib/test/test_email/test__header_value_parser.py
+++ b/Lib/test/test_email/test__header_value_parser.py
@@ -2973,6 +2973,29 @@ def 
test_address_list_with_specials_in_long_quoted_string(self):
             with self.subTest(to=to):
                 self._test(parser.get_address_list(to)[0], folded, 
policy=policy)
 
+    def test_address_list_with_long_unwrapable_comment(self):
+        policy = self.policy.clone(max_line_length=40)
+        cases = [
+            # (to, folded)
+            ('(loremipsumdolorsitametconsecteturadipi)<[email protected]>',
+             '(loremipsumdolorsitametconsecteturadipi)<[email protected]>\n'),
+            ('<[email protected]>(loremipsumdolorsitametconsecteturadipi)',
+             '<[email protected]>(loremipsumdolorsitametconsecteturadipi)\n'),
+            ('(loremipsum dolorsitametconsecteturadipi)<[email protected]>',
+             '(loremipsum dolorsitametconsecteturadipi)<[email protected]>\n'),
+             ('<[email protected]>(loremipsum dolorsitametconsecteturadipi)',
+             '<[email protected]>(loremipsum\n 
dolorsitametconsecteturadipi)\n'),
+            ('(Escaped \\( \\) chars \\\\ in comments stay 
escaped)<[email protected]>',
+             '(Escaped \\( \\) chars \\\\ in comments stay\n 
escaped)<[email protected]>\n'),
+            
('((loremipsum)(loremipsum)(loremipsum)(loremipsum))<[email protected]>',
+             
'((loremipsum)(loremipsum)(loremipsum)(loremipsum))<[email protected]>\n'),
+            ('((loremipsum)(loremipsum)(loremipsum) 
(loremipsum))<[email protected]>',
+             '((loremipsum)(loremipsum)(loremipsum)\n 
(loremipsum))<[email protected]>\n'),
+        ]
+        for (to, folded) in cases:
+            with self.subTest(to=to):
+                self._test(parser.get_address_list(to)[0], folded, 
policy=policy)
+
     def test_address_list_with_specials_in_encoded_word(self):
         # An encoded-word parsed from a structured header must remain
         # encoded when it contains specials. Regression for gh-121284.
diff --git 
a/Misc/NEWS.d/next/Security/2026-01-16-14-40-31.gh-issue-143935.U2YtKl.rst 
b/Misc/NEWS.d/next/Security/2026-01-16-14-40-31.gh-issue-143935.U2YtKl.rst
new file mode 100644
index 00000000000000..c3d864936884ac
--- /dev/null
+++ b/Misc/NEWS.d/next/Security/2026-01-16-14-40-31.gh-issue-143935.U2YtKl.rst
@@ -0,0 +1,6 @@
+Fixed a bug in the folding of comments when flattening an email message
+using a modern email policy. Comments consisting of a very long sequence of
+non-foldable characters could trigger a forced line wrap that omitted the
+required leading space on the continuation line, causing the remainder of
+the comment to be interpreted as a new header field. This enabled header
+injection with carefully crafted inputs.

++++++ CVE-2025-15282-urllib-ctrl-chars.patch ++++++
>From d6c6f0880dbc6ffd770b859087f4cd749a1d0dbb Mon Sep 17 00:00:00 2001
From: Seth Michael Larson <[email protected]>
Date: Tue, 20 Jan 2026 14:45:58 -0600
Subject: [PATCH] [3.10] gh-143925: Reject control characters in data: URL
 mediatypes (cherry picked from commit
 f25509e78e8be6ea73c811ac2b8c928c28841b9f) (cherry picked from commit
 2c9c746077d8119b5bcf5142316992e464594946)

Co-authored-by: Seth Michael Larson <[email protected]>
---
 Lib/test/test_urllib.py                                                  |    
8 ++++++++
 Lib/urllib/request.py                                                    |    
5 +++++
 Misc/NEWS.d/next/Security/2026-01-16-11-51-19.gh-issue-143925.mrtcHW.rst |    
1 +
 3 files changed, 14 insertions(+)
 create mode 100644 
Misc/NEWS.d/next/Security/2026-01-16-11-51-19.gh-issue-143925.mrtcHW.rst

Index: Python-3.10.19/Lib/test/test_urllib.py
===================================================================
--- Python-3.10.19.orig/Lib/test/test_urllib.py 2026-02-12 01:05:56.127447144 
+0100
+++ Python-3.10.19/Lib/test/test_urllib.py      2026-02-12 01:08:02.226352573 
+0100
@@ -11,6 +11,7 @@
 from test import support
 from test.support import os_helper
 from test.support import warnings_helper
+from test.support import control_characters_c0
 import os
 try:
     import ssl
@@ -683,6 +684,13 @@
         # missing padding character
         self.assertRaises(ValueError,urllib.request.urlopen,'data:;base64,Cg=')
 
+    def test_invalid_mediatype(self):
+        for c0 in control_characters_c0():
+            self.assertRaises(ValueError,urllib.request.urlopen,
+                              f'data:text/html;{c0},data')
+        for c0 in control_characters_c0():
+            self.assertRaises(ValueError,urllib.request.urlopen,
+                              f'data:text/html{c0};base64,ZGF0YQ==')
 
 class urlretrieve_FileTests(unittest.TestCase):
     """Test urllib.urlretrieve() on local files"""
Index: Python-3.10.19/Lib/urllib/request.py
===================================================================
--- Python-3.10.19.orig/Lib/urllib/request.py   2026-02-12 01:05:56.627830069 
+0100
+++ Python-3.10.19/Lib/urllib/request.py        2026-02-12 01:08:02.226810828 
+0100
@@ -1654,6 +1654,11 @@
         scheme, data = url.split(":",1)
         mediatype, data = data.split(",",1)
 
+        # Disallow control characters within mediatype.
+        if re.search(r"[\x00-\x1F\x7F]", mediatype):
+            raise ValueError(
+                "Control characters not allowed in data: mediatype")
+
         # even base64 encoded data URLs might be quoted so unquote in any case:
         data = unquote_to_bytes(data)
         if mediatype.endswith(";base64"):
Index: 
Python-3.10.19/Misc/NEWS.d/next/Security/2026-01-16-11-51-19.gh-issue-143925.mrtcHW.rst
===================================================================
--- /dev/null   1970-01-01 00:00:00.000000000 +0000
+++ 
Python-3.10.19/Misc/NEWS.d/next/Security/2026-01-16-11-51-19.gh-issue-143925.mrtcHW.rst
     2026-02-12 01:08:02.227192287 +0100
@@ -0,0 +1 @@
+Reject control characters in ``data:`` URL media types.

++++++ CVE-2025-15366-imap-ctrl-chars.patch ++++++
>From 7485ee5e2cf81d3e5ad0d9c3be73cecd2ab4eec7 Mon Sep 17 00:00:00 2001
From: Seth Michael Larson <[email protected]>
Date: Fri, 16 Jan 2026 10:54:09 -0600
Subject: [PATCH 1/2] Add 'test.support' fixture for C0 control characters

---
 Lib/imaplib.py                                                           |    
4 +++-
 Lib/test/test_imaplib.py                                                 |    
6 ++++++
 Misc/NEWS.d/next/Security/2026-01-16-11-41-06.gh-issue-143921.AeCOor.rst |    
1 +
 3 files changed, 10 insertions(+), 1 deletion(-)

Index: Python-3.10.19/Lib/imaplib.py
===================================================================
--- Python-3.10.19.orig/Lib/imaplib.py  2026-02-12 01:05:53.821319313 +0100
+++ Python-3.10.19/Lib/imaplib.py       2026-02-12 01:06:28.558652908 +0100
@@ -132,7 +132,7 @@
 # We compile these in _mode_xxx.
 _Literal = br'.*{(?P<size>\d+)}$'
 _Untagged_status = br'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?'
-
+_control_chars = re.compile(b'[\x00-\x1F\x7F]')
 
 
 class IMAP4:
@@ -994,6 +994,8 @@
             if arg is None: continue
             if isinstance(arg, str):
                 arg = bytes(arg, self._encoding)
+            if _control_chars.search(arg):
+                raise ValueError("Control characters not allowed in commands")
             data = data + b' ' + arg
 
         literal = self.literal
Index: Python-3.10.19/Lib/test/test_imaplib.py
===================================================================
--- Python-3.10.19.orig/Lib/test/test_imaplib.py        2026-02-12 
01:05:55.293033311 +0100
+++ Python-3.10.19/Lib/test/test_imaplib.py     2026-02-12 01:07:45.387053336 
+0100
@@ -538,6 +538,12 @@
         self.assertEqual(data[0], b'Returned to authenticated state. 
(Success)')
         self.assertEqual(client.state, 'AUTH')
 
+    def test_control_characters(self):
+        client, _ = self._setup(SimpleIMAPHandler)
+        for c0 in support.control_characters_c0():
+            with self.assertRaises(ValueError):
+                client.login(f'user{c0}', 'pass')
+
 
 class NewIMAPTests(NewIMAPTestsMixin, unittest.TestCase):
     imap_class = imaplib.IMAP4
Index: 
Python-3.10.19/Misc/NEWS.d/next/Security/2026-01-16-11-41-06.gh-issue-143921.AeCOor.rst
===================================================================
--- /dev/null   1970-01-01 00:00:00.000000000 +0000
+++ 
Python-3.10.19/Misc/NEWS.d/next/Security/2026-01-16-11-41-06.gh-issue-143921.AeCOor.rst
     2026-02-12 01:06:28.559224837 +0100
@@ -0,0 +1 @@
+Reject control characters in IMAP commands.

++++++ CVE-2025-15367-poplib-ctrl-chars.patch ++++++
>From b6f733b285b1c4f27dacb5c2e1f292c914e8b933 Mon Sep 17 00:00:00 2001
From: Seth Michael Larson <[email protected]>
Date: Fri, 16 Jan 2026 10:54:09 -0600
Subject: [PATCH 1/2] Add 'test.support' fixture for C0 control characters

---
 Lib/poplib.py                                                            |    
2 ++
 Lib/test/test_poplib.py                                                  |    
8 ++++++++
 Misc/NEWS.d/next/Security/2026-01-16-11-43-47.gh-issue-143923.DuytMe.rst |    
1 +
 3 files changed, 11 insertions(+)

Index: Python-3.10.19/Lib/poplib.py
===================================================================
--- Python-3.10.19.orig/Lib/poplib.py   2026-02-12 01:05:54.262197434 +0100
+++ Python-3.10.19/Lib/poplib.py        2026-02-12 01:12:02.945762019 +0100
@@ -122,6 +122,8 @@
     def _putcmd(self, line):
         if self._debugging: print('*cmd*', repr(line))
         line = bytes(line, self.encoding)
+        if re.search(b'[\x00-\x1F\x7F]', line):
+            raise ValueError('Control characters not allowed in commands')
         self._putline(line)
 
 
Index: Python-3.10.19/Lib/test/test_poplib.py
===================================================================
--- Python-3.10.19.orig/Lib/test/test_poplib.py 2026-02-12 01:05:55.796995175 
+0100
+++ Python-3.10.19/Lib/test/test_poplib.py      2026-02-12 01:12:32.837694637 
+0100
@@ -15,6 +15,7 @@
 from test.support import hashlib_helper
 from test.support import socket_helper
 from test.support import threading_helper
+from test.support import control_characters_c0
 
 import warnings
 with warnings.catch_warnings():
@@ -365,6 +366,13 @@
         self.assertIsNone(self.client.sock)
         self.assertIsNone(self.client.file)
 
+    def test_control_characters(self):
+        for c0 in control_characters_c0():
+            with self.assertRaises(ValueError):
+                self.client.user(f'user{c0}')
+            with self.assertRaises(ValueError):
+                self.client.pass_(f'{c0}pass')
+
     @requires_ssl
     def test_stls_capa(self):
         capa = self.client.capa()
Index: 
Python-3.10.19/Misc/NEWS.d/next/Security/2026-01-16-11-43-47.gh-issue-143923.DuytMe.rst
===================================================================
--- /dev/null   1970-01-01 00:00:00.000000000 +0000
+++ 
Python-3.10.19/Misc/NEWS.d/next/Security/2026-01-16-11-43-47.gh-issue-143923.DuytMe.rst
     2026-02-12 01:12:02.946199975 +0100
@@ -0,0 +1 @@
+Reject control characters in POP3 commands.

++++++ CVE-2026-0672-http-hdr-inject-cookie-Morsel.patch ++++++
>From 57c5ecd7e61fbb24e7de76eafd95332bd0ae4dea Mon Sep 17 00:00:00 2001
From: Seth Michael Larson <[email protected]>
Date: Tue, 20 Jan 2026 15:23:42 -0600
Subject: [PATCH] [3.10] gh-143919: Reject control characters in http cookies
 (cherry picked from commit 95746b3a13a985787ef53b977129041971ed7f70)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Co-authored-by: Seth Michael Larson <[email protected]>
Co-authored-by: Bartosz Sławecki <[email protected]>
Co-authored-by: sobolevn <[email protected]>
---
 Doc/library/http.cookies.rst                  |  4 +-
 Lib/http/cookies.py                           | 25 +++++++--
 Lib/test/test_http_cookies.py                 | 52 +++++++++++++++++--
 ...-01-16-11-13-15.gh-issue-143919.kchwZV.rst |  1 +
 4 files changed, 73 insertions(+), 9 deletions(-)
 create mode 100644 
Misc/NEWS.d/next/Security/2026-01-16-11-13-15.gh-issue-143919.kchwZV.rst

diff --git a/Doc/library/http.cookies.rst b/Doc/library/http.cookies.rst
index a2c1eb00d8b33d..4cb563c230ea5e 100644
--- a/Doc/library/http.cookies.rst
+++ b/Doc/library/http.cookies.rst
@@ -270,9 +270,9 @@ The following example demonstrates how to use the 
:mod:`http.cookies` module.
    Set-Cookie: chips=ahoy
    Set-Cookie: vienna=finger
    >>> C = cookies.SimpleCookie()
-   >>> C.load('keebler="E=everybody; L=\\"Loves\\"; fudge=\\012;";')
+   >>> C.load('keebler="E=everybody; L=\\"Loves\\"; fudge=;";')
    >>> print(C)
-   Set-Cookie: keebler="E=everybody; L=\"Loves\"; fudge=\012;"
+   Set-Cookie: keebler="E=everybody; L=\"Loves\"; fudge=;"
    >>> C = cookies.SimpleCookie()
    >>> C["oreo"] = "doublestuff"
    >>> C["oreo"]["path"] = "/"
diff --git a/Lib/http/cookies.py b/Lib/http/cookies.py
index 2c1f021d0abede..5cfa7a8072c7f7 100644
--- a/Lib/http/cookies.py
+++ b/Lib/http/cookies.py
@@ -87,9 +87,9 @@
 such trickeries do not confuse it.
 
    >>> C = cookies.SimpleCookie()
-   >>> C.load('keebler="E=everybody; L=\\"Loves\\"; fudge=\\012;";')
+   >>> C.load('keebler="E=everybody; L=\\"Loves\\"; fudge=;";')
    >>> print(C)
-   Set-Cookie: keebler="E=everybody; L=\"Loves\"; fudge=\012;"
+   Set-Cookie: keebler="E=everybody; L=\"Loves\"; fudge=;"
 
 Each element of the Cookie also supports all of the RFC 2109
 Cookie attributes.  Here's an example which sets the Path
@@ -170,6 +170,15 @@ class CookieError(Exception):
 })
 
 _is_legal_key = re.compile('[%s]+' % re.escape(_LegalChars)).fullmatch
+_control_character_re = re.compile(r'[\x00-\x1F\x7F]')
+
+
+def _has_control_character(*val):
+    """Detects control characters within a value.
+    Supports any type, as header values can be any type.
+    """
+    return any(_control_character_re.search(str(v)) for v in val)
+
 
 def _quote(str):
     r"""Quote a string for use in a cookie header.
@@ -292,12 +301,16 @@ def __setitem__(self, K, V):
         K = K.lower()
         if not K in self._reserved:
             raise CookieError("Invalid attribute %r" % (K,))
+        if _has_control_character(K, V):
+            raise CookieError(f"Control characters are not allowed in cookies 
{K!r} {V!r}")
         dict.__setitem__(self, K, V)
 
     def setdefault(self, key, val=None):
         key = key.lower()
         if key not in self._reserved:
             raise CookieError("Invalid attribute %r" % (key,))
+        if _has_control_character(key, val):
+            raise CookieError("Control characters are not allowed in cookies 
%r %r" % (key, val,))
         return dict.setdefault(self, key, val)
 
     def __eq__(self, morsel):
@@ -333,6 +346,9 @@ def set(self, key, val, coded_val):
             raise CookieError('Attempt to set a reserved key %r' % (key,))
         if not _is_legal_key(key):
             raise CookieError('Illegal key %r' % (key,))
+        if _has_control_character(key, val, coded_val):
+            raise CookieError(
+                "Control characters are not allowed in cookies %r %r %r" % 
(key, val, coded_val,))
 
         # It's a good key, so save it.
         self._key = key
@@ -484,7 +500,10 @@ def output(self, attrs=None, header="Set-Cookie:", 
sep="\015\012"):
         result = []
         items = sorted(self.items())
         for key, value in items:
-            result.append(value.output(attrs, header))
+            value_output = value.output(attrs, header)
+            if _has_control_character(value_output):
+                raise CookieError("Control characters are not allowed in 
cookies")
+            result.append(value_output)
         return sep.join(result)
 
     __str__ = output
diff --git a/Lib/test/test_http_cookies.py b/Lib/test/test_http_cookies.py
index 644e75cd5b742e..1f2c049fa811fa 100644
--- a/Lib/test/test_http_cookies.py
+++ b/Lib/test/test_http_cookies.py
@@ -17,10 +17,10 @@ def test_basic(self):
              'repr': "<SimpleCookie: chips='ahoy' vienna='finger'>",
              'output': 'Set-Cookie: chips=ahoy\nSet-Cookie: vienna=finger'},
 
-            {'data': 'keebler="E=mc2; L=\\"Loves\\"; fudge=\\012;"',
-             'dict': {'keebler' : 'E=mc2; L="Loves"; fudge=\012;'},
-             'repr': '''<SimpleCookie: keebler='E=mc2; L="Loves"; 
fudge=\\n;'>''',
-             'output': 'Set-Cookie: keebler="E=mc2; L=\\"Loves\\"; 
fudge=\\012;"'},
+            {'data': 'keebler="E=mc2; L=\\"Loves\\"; fudge=;"',
+             'dict': {'keebler' : 'E=mc2; L="Loves"; fudge=;'},
+             'repr': '''<SimpleCookie: keebler='E=mc2; L="Loves"; fudge=;'>''',
+             'output': 'Set-Cookie: keebler="E=mc2; L=\\"Loves\\"; fudge=;"'},
 
             # Check illegal cookies that have an '=' char in an unquoted value
             {'data': 'keebler=E=mc2',
@@ -517,6 +517,50 @@ def test_repr(self):
                 r'Set-Cookie: key=coded_val; '
                 r'expires=\w+, \d+ \w+ \d+ \d+:\d+:\d+ \w+')
 
+    def test_control_characters(self):
+        for c0 in support.control_characters_c0():
+            morsel = cookies.Morsel()
+
+            # .__setitem__()
+            with self.assertRaises(cookies.CookieError):
+                morsel[c0] = "val"
+            with self.assertRaises(cookies.CookieError):
+                morsel["path"] = c0
+
+            # .setdefault()
+            with self.assertRaises(cookies.CookieError):
+                morsel.setdefault("path", c0)
+            with self.assertRaises(cookies.CookieError):
+                morsel.setdefault(c0, "val")
+
+            # .set()
+            with self.assertRaises(cookies.CookieError):
+                morsel.set(c0, "val", "coded-value")
+            with self.assertRaises(cookies.CookieError):
+                morsel.set("path", c0, "coded-value")
+            with self.assertRaises(cookies.CookieError):
+                morsel.set("path", "val", c0)
+
+    def test_control_characters_output(self):
+        # Tests that even if the internals of Morsel are modified
+        # that a call to .output() has control character safeguards.
+        for c0 in support.control_characters_c0():
+            morsel = cookies.Morsel()
+            morsel.set("key", "value", "coded-value")
+            morsel._key = c0  # Override private variable.
+            cookie = cookies.SimpleCookie()
+            cookie["cookie"] = morsel
+            with self.assertRaises(cookies.CookieError):
+                cookie.output()
+
+            morsel = cookies.Morsel()
+            morsel.set("key", "value", "coded-value")
+            morsel._coded_value = c0  # Override private variable.
+            cookie = cookies.SimpleCookie()
+            cookie["cookie"] = morsel
+            with self.assertRaises(cookies.CookieError):
+                cookie.output()
+
 def test_main():
     run_unittest(CookieTests, MorselTests)
     run_doctest(cookies)
diff --git 
a/Misc/NEWS.d/next/Security/2026-01-16-11-13-15.gh-issue-143919.kchwZV.rst 
b/Misc/NEWS.d/next/Security/2026-01-16-11-13-15.gh-issue-143919.kchwZV.rst
new file mode 100644
index 00000000000000..788c3e4ac2ebf7
--- /dev/null
+++ b/Misc/NEWS.d/next/Security/2026-01-16-11-13-15.gh-issue-143919.kchwZV.rst
@@ -0,0 +1 @@
+Reject control characters in :class:`http.cookies.Morsel` fields and values.

++++++ CVE-2026-0865-wsgiref-ctrl-chars.patch ++++++
>From 123bfbbe9074ef7fa28e1e7b25575665296560fa Mon Sep 17 00:00:00 2001
From: "Gregory P. Smith" <[email protected]>
Date: Sat, 17 Jan 2026 10:23:57 -0800
Subject: [PATCH] [3.10] gh-143916: Reject control characters in
 wsgiref.headers.Headers (GH-143917) (GH-143973)

gh-143916: Reject control characters in wsgiref.headers.Headers  (GH-143917)

* Add 'test.support' fixture for C0 control characters
* gh-143916: Reject control characters in wsgiref.headers.Headers

(cherry picked from commit f7fceed79ca1bceae8dbe5ba5bc8928564da7211)
(cherry picked from commit 22e4d55285cee52bc4dbe061324e5f30bd4dee58)

Co-authored-by: Gregory P. Smith <[email protected]>
Co-authored-by: Seth Michael Larson <[email protected]>
---
 Lib/test/support/__init__.py                         |  7 +++++++
 Lib/test/test_wsgiref.py                             | 12 +++++++++++-
 Lib/wsgiref/headers.py                               |  3 +++
 .../2026-01-16-11-07-36.gh-issue-143916.dpWeOD.rst   |  2 ++
 4 files changed, 23 insertions(+), 1 deletion(-)
 create mode 100644 
Misc/NEWS.d/next/Security/2026-01-16-11-07-36.gh-issue-143916.dpWeOD.rst

diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
index 0d3b9634f10248..d0492fe1914343 100644
--- a/Lib/test/support/__init__.py
+++ b/Lib/test/support/__init__.py
@@ -2157,3 +2157,10 @@ def adjust_int_max_str_digits(max_digits):
         yield
     finally:
         sys.set_int_max_str_digits(current)
+
+
+def control_characters_c0() -> list[str]:
+    """Returns a list of C0 control characters as strings.
+    C0 control characters defined as the byte range 0x00-0x1F, and 0x7F.
+    """
+    return [chr(c) for c in range(0x00, 0x20)] + ["\x7F"]
diff --git a/Lib/test/test_wsgiref.py b/Lib/test/test_wsgiref.py
index 42094f467731d4..01ca51ba458587 100644
--- a/Lib/test/test_wsgiref.py
+++ b/Lib/test/test_wsgiref.py
@@ -1,6 +1,6 @@
 from unittest import mock
 from test import support
-from test.support import socket_helper
+from test.support import socket_helper, control_characters_c0
 from test.support import warnings_helper
 from test.test_httpservers import NoLogRequestHandler
 from unittest import TestCase
@@ -527,6 +527,16 @@ def testExtras(self):
             '\r\n'
         )
 
+    def testRaisesControlCharacters(self):
+        headers = Headers()
+        for c0 in control_characters_c0():
+            self.assertRaises(ValueError, headers.__setitem__, f"key{c0}", 
"val")
+            self.assertRaises(ValueError, headers.__setitem__, "key", 
f"val{c0}")
+            self.assertRaises(ValueError, headers.add_header, f"key{c0}", 
"val", param="param")
+            self.assertRaises(ValueError, headers.add_header, "key", 
f"val{c0}", param="param")
+            self.assertRaises(ValueError, headers.add_header, "key", "val", 
param=f"param{c0}")
+
+
 class ErrorHandler(BaseCGIHandler):
     """Simple handler subclass for testing BaseHandler"""
 
diff --git a/Lib/wsgiref/headers.py b/Lib/wsgiref/headers.py
index fab851c5a44430..fd98e85d75492b 100644
--- a/Lib/wsgiref/headers.py
+++ b/Lib/wsgiref/headers.py
@@ -9,6 +9,7 @@
 # existence of which force quoting of the parameter value.
 import re
 tspecials = re.compile(r'[ \(\)<>@,;:\\"/\[\]\?=]')
+_control_chars_re = re.compile(r'[\x00-\x1F\x7F]')
 
 def _formatparam(param, value=None, quote=1):
     """Convenience function to format and return a key=value pair.
@@ -41,6 +42,8 @@ def __init__(self, headers=None):
     def _convert_string_type(self, value):
         """Convert/check value type."""
         if type(value) is str:
+            if _control_chars_re.search(value):
+                raise ValueError("Control characters not allowed in headers")
             return value
         raise AssertionError("Header names/values must be"
             " of type str (got {0})".format(repr(value)))
diff --git 
a/Misc/NEWS.d/next/Security/2026-01-16-11-07-36.gh-issue-143916.dpWeOD.rst 
b/Misc/NEWS.d/next/Security/2026-01-16-11-07-36.gh-issue-143916.dpWeOD.rst
new file mode 100644
index 00000000000000..44bd0b27059f94
--- /dev/null
+++ b/Misc/NEWS.d/next/Security/2026-01-16-11-07-36.gh-issue-143916.dpWeOD.rst
@@ -0,0 +1,2 @@
+Reject C0 control characters within wsgiref.headers.Headers fields, values,
+and parameters.

++++++ _scmsync.obsinfo ++++++
--- /var/tmp/diff_new_pack.fl5Wb2/_old  2026-02-13 12:46:44.794306338 +0100
+++ /var/tmp/diff_new_pack.fl5Wb2/_new  2026-02-13 12:46:44.798306505 +0100
@@ -1,6 +1,6 @@
-mtime: 1766355292
-commit: 9fe71b82c6ef2ff67675dd29a826fc03422401ceb7729767876384a8deb10ae5
+mtime: 1770892390
+commit: 539e53b74e21ccb33f66e62a62b47bbff0587f63094b112fc84f996e1b1deded
 url: https://src.opensuse.org/python-interpreters/python310.git
-revision: 9fe71b82c6ef2ff67675dd29a826fc03422401ceb7729767876384a8deb10ae5
+revision: 539e53b74e21ccb33f66e62a62b47bbff0587f63094b112fc84f996e1b1deded
 projectscmsync: https://src.opensuse.org/python-interpreters/_ObsPrj
 

++++++ build.specials.obscpio ++++++

++++++ build.specials.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/.gitignore new/.gitignore
--- old/.gitignore      1970-01-01 01:00:00.000000000 +0100
+++ new/.gitignore      2026-02-12 11:33:40.000000000 +0100
@@ -0,0 +1,5 @@
+.osc
+*.obscpio
+_build*
+.pbuild
+python310-*-build/

Reply via email to