Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-Twisted for openSUSE:Factory
checked in at 2026-10-14 18:34:15
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-Twisted (Old)
and /work/SRC/openSUSE:Factory/.python-Twisted.new.3698736 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-Twisted"
Wed Oct 14 18:34:15 2026 rev:81 rq:1384623 version:26.4.0
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-Twisted/python-Twisted.changes
2026-06-16 18:29:24.673550913 +0200
+++
/work/SRC/openSUSE:Factory/.python-Twisted.new.3698736/python-Twisted.changes
2026-10-14 18:34:41.694793324 +0200
@@ -1,0 +2,8 @@
+Tue Oct 13 12:22:23 UTC 2026 - Nico Krapp <[email protected]>
+
+- CVE-2026-106454: allowing nested or otherwise expensive regular
+ expression constructs to cause catastrophic backtracking when
+ matched against mailbox names (bsc#1284420)
+ * added CVE-2026-106454.patch
+
+-------------------------------------------------------------------
New:
----
CVE-2026-106454.patch
----------(New B)----------
New: matched against mailbox names (bsc#1284420)
* added CVE-2026-106454.patch
----------(New E)----------
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-Twisted.spec ++++++
--- /var/tmp/diff_new_pack.UmDWzd/_old 2026-10-14 18:34:42.800840159 +0200
+++ /var/tmp/diff_new_pack.UmDWzd/_new 2026-10-14 18:34:42.801840202 +0200
@@ -48,6 +48,8 @@
Patch3: remove-dependency-version-upper-bounds.patch
# PATCH-FIX-UPSTREAM: https://github.com/twisted/twisted/pull/12661
Patch4: pycryptography-use-csr.patch
+# PATCH-FIX-UPSTREAM CVE-2026-106454.patch bsc#1284420
gh#twisted/twisted@f570190
+Patch5: CVE-2026-106454.patch
BuildRequires: %{python_module base >= 3.9}
BuildRequires: %{python_module hatch-fancy-pypi-readme}
BuildRequires: %{python_module hatchling}
++++++ CVE-2026-106454.patch ++++++
>From 73fe2b00519a43aeebdb501247222b3a04b45700 Mon Sep 17 00:00:00 2001
From: Glyph <[email protected]>
Date: Fri, 14 Aug 2026 18:46:34 -0700
Subject: [PATCH 1/7] add a type annotation since we're about to make changes
---
src/twisted/mail/imap4.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/twisted/mail/imap4.py b/src/twisted/mail/imap4.py
index fdc53c10a46..5a9023f22af 100644
--- a/src/twisted/mail/imap4.py
+++ b/src/twisted/mail/imap4.py
@@ -4593,7 +4593,7 @@ def Not(query):
return f"(NOT {query})"
-def wildcardToRegexp(wildcard, delim=None):
+def wildcardToRegexp(wildcard: str, delim: str | None = None) ->
re.Pattern[str]:
wildcard = wildcard.replace("*", "(?:.*?)")
if delim is None:
wildcard = wildcard.replace("%", "(?:.*?)")
>From b7eb623dd6391b14c149785c3972e4b077e05d6c Mon Sep 17 00:00:00 2001
From: Glyph <[email protected]>
Date: Fri, 14 Aug 2026 18:48:24 -0700
Subject: [PATCH 2/7] news fragment
---
src/twisted/mail/newsfragments/12787.bugfix | 1 +
1 file changed, 1 insertion(+)
create mode 100644 src/twisted/mail/newsfragments/12787.bugfix
diff --git a/src/twisted/mail/newsfragments/12787.bugfix
b/src/twisted/mail/newsfragments/12787.bugfix
new file mode 100644
index 00000000000..d8fd47ab1fd
--- /dev/null
+++ b/src/twisted/mail/newsfragments/12787.bugfix
@@ -0,0 +1 @@
+twisted.mail.imap4 no longer allows clients to specify arbitrary regexes.
>From ed5c194b78b501120c8548767cf3033e3a81d3ba Mon Sep 17 00:00:00 2001
From: Glyph <[email protected]>
Date: Fri, 14 Aug 2026 18:57:39 -0700
Subject: [PATCH 3/7] failing test that uses a python regexp
---
src/twisted/mail/test/test_imap.py | 21 +++++++++++++++++++--
1 file changed, 19 insertions(+), 2 deletions(-)
diff --git a/src/twisted/mail/test/test_imap.py
b/src/twisted/mail/test/test_imap.py
index 9fdc97ed735..bd7eef8b7c7 100644
--- a/src/twisted/mail/test/test_imap.py
+++ b/src/twisted/mail/test/test_imap.py
@@ -6,6 +6,7 @@
"""
Test case for twisted.mail.imap4
"""
+
from __future__ import annotations
import base64
@@ -17,6 +18,7 @@
from collections import OrderedDict
from io import BytesIO
from itertools import chain
+from typing import Any
from unittest import skipIf
from zope.interface import implementer
@@ -2554,7 +2556,7 @@ def unsubscribe():
)
return d
- def _listSetup(self, f):
+ def _listSetup(self, f: object) -> Deferred[Any]:
SimpleServer.theAccount.addMailbox("root/subthing")
SimpleServer.theAccount.addMailbox("root/another-thing")
SimpleServer.theAccount.addMailbox("non-root/subthing")
@@ -2621,6 +2623,21 @@ def lsub():
d.addCallback(self.assertEqual, [(SimpleMailbox.flags, "/",
"ROOT/SUBTHING")])
return d
+ def test_LSubNoRegex(self) -> Deferred[None]:
+ """
+ LSUB commands should use IMAP wildcards, and not be treated as Python
+ regexes.
+ """
+ SimpleServer.theAccount.subscribe("ROOT/SUBTHING")
+
+ def lsub():
+ return self.client.lsub("root", "root/.*")
+
+ d = self._listSetup(lsub)
+ d.addCallback(self.assertListDelimiterAndMailboxAreStrings)
+ d.addCallback(self.assertEqual, [])
+ return d
+
def testStatus(self):
SimpleServer.theAccount.addMailbox("root/subthing")
@@ -7125,7 +7142,7 @@ def cbCopy(results):
return d.addCallback(cbCopy)
-@skipIf(not ClientTLSContext, "OpenSSL not present") #
type:ignore[truthy-function]
+@skipIf(not ClientTLSContext, "OpenSSL not present") # type:
ignore[truthy-function]
@skipIf(not interfaces.IReactorSSL(reactor, None), "Reactor doesn't support
SSL")
class TLSTests(IMAP4HelperMixin, TestCase):
serverCTX = None
>From 2f8a3c29246f4eb324690e06a9767a11dc4aec9f Mon Sep 17 00:00:00 2001
From: Glyph <[email protected]>
Date: Fri, 14 Aug 2026 18:57:50 -0700
Subject: [PATCH 4/7] make it pass by escaping everything else
---
src/twisted/mail/imap4.py | 20 ++++++++++++++------
1 file changed, 14 insertions(+), 6 deletions(-)
diff --git a/src/twisted/mail/imap4.py b/src/twisted/mail/imap4.py
index 5a9023f22af..52dd1553a05 100644
--- a/src/twisted/mail/imap4.py
+++ b/src/twisted/mail/imap4.py
@@ -4594,12 +4594,20 @@ def Not(query):
def wildcardToRegexp(wildcard: str, delim: str | None = None) ->
re.Pattern[str]:
- wildcard = wildcard.replace("*", "(?:.*?)")
- if delim is None:
- wildcard = wildcard.replace("%", "(?:.*?)")
- else:
- wildcard = wildcard.replace("%", "(?:(?:[^%s])*?)" % re.escape(delim))
- return re.compile(wildcard, re.I)
+ # Split on the two IMAP wildcards, escape everything else
+ parts = re.split(r"([*%])", wildcard)
+ result = []
+ for p in parts:
+ if p == "*":
+ result.append("(?:.*?)")
+ elif p == "%":
+ if delim is None:
+ result.append("(?:.*?)")
+ else:
+ result.append(f"(?:(?:[^{re.escape(delim)}])*?)")
+ else:
+ result.append(re.escape(p))
+ return re.compile("".join(result), re.I)
def splitQuoted(s):
>From 47fca7ef2e2333b8fbdeec659c0df7934123b13e Mon Sep 17 00:00:00 2001
From: Glyph <[email protected]>
Date: Sat, 15 Aug 2026 09:04:00 -0700
Subject: [PATCH 5/7] add more potential matches to exclude to be sure
Co-authored-by: Adi Roiban <[email protected]>
---
src/twisted/mail/test/test_imap.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/twisted/mail/test/test_imap.py
b/src/twisted/mail/test/test_imap.py
index bd7eef8b7c7..872ab3d392c 100644
--- a/src/twisted/mail/test/test_imap.py
+++ b/src/twisted/mail/test/test_imap.py
@@ -2629,6 +2629,8 @@ def test_LSubNoRegex(self) -> Deferred[None]:
regexes.
"""
SimpleServer.theAccount.subscribe("ROOT/SUBTHING")
+ SimpleServer.theAccount.subscribe("ROOT/.SUBMATCH")
+ SimpleServer.theAccount.subscribe("ROOT/.DEEP/MATCH")
def lsub():
return self.client.lsub("root", "root/.*")
>From 67a34dbb8bc3f4ff387d6665e9870c3a76ae625b Mon Sep 17 00:00:00 2001
From: Glyph <[email protected]>
Date: Sat, 15 Aug 2026 09:18:24 -0700
Subject: [PATCH 6/7] per code review, docstring for wildcardToRegexp
---
src/twisted/mail/imap4.py | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/src/twisted/mail/imap4.py b/src/twisted/mail/imap4.py
index 52dd1553a05..4ca0463f0c6 100644
--- a/src/twisted/mail/imap4.py
+++ b/src/twisted/mail/imap4.py
@@ -4594,7 +4594,21 @@ def Not(query):
def wildcardToRegexp(wildcard: str, delim: str | None = None) ->
re.Pattern[str]:
- # Split on the two IMAP wildcards, escape everything else
+ """
+ Convert what the IMAP describes as a "mailbox name with possible wildcards"
+ into a regular expression that will match a full mailbox name.
+
+ @param wildcard: the mailbox name matching expression which may contain
+ IMAP wildcards, e.g. C{*} and C{%}. Note that this is I{not} treated
+ as a regular expression itself, and any regex syntax will be matched
+ literally.
+
+ @param delim: the delimiter between IMAP path elements, if any. Note that
+ this is always C{"/"} when used by Twisted's IMAP server.
+
+ @return: a L{re.Pattern} that will match IMAP mailbox names with the given
+ wildcards.
+ """
parts = re.split(r"([*%])", wildcard)
result = []
for p in parts:
>From 5a020d27c67f19d7592af3f73f5f78f9cac85344 Mon Sep 17 00:00:00 2001
From: Glyph <[email protected]>
Date: Sat, 15 Aug 2026 09:37:40 -0700
Subject: [PATCH 7/7] per code review test some more cases
---
src/twisted/mail/test/test_imap.py | 22 +++++++++++++++++++---
1 file changed, 19 insertions(+), 3 deletions(-)
diff --git a/src/twisted/mail/test/test_imap.py
b/src/twisted/mail/test/test_imap.py
index 872ab3d392c..b4bebcce3f4 100644
--- a/src/twisted/mail/test/test_imap.py
+++ b/src/twisted/mail/test/test_imap.py
@@ -2628,16 +2628,32 @@ def test_LSubNoRegex(self) -> Deferred[None]:
LSUB commands should use IMAP wildcards, and not be treated as Python
regexes.
"""
+ # Subscribe to existing mailbox.
SimpleServer.theAccount.subscribe("ROOT/SUBTHING")
- SimpleServer.theAccount.subscribe("ROOT/.SUBMATCH")
- SimpleServer.theAccount.subscribe("ROOT/.DEEP/MATCH")
+
+ for newMailbox in [
+ # Verify that dot is treated literally.
+ "ROOT/.SUBMATCH",
+ # And can match nested things, since we're testing *.
+ "ROOT/.DEEP/MATCH",
+ # But only at the start of the match.
+ "ROOT/DEEP/.MATCH",
+ ]:
+ SimpleServer.theAccount.addMailbox(newMailbox)
+ SimpleServer.theAccount.subscribe(newMailbox)
def lsub():
return self.client.lsub("root", "root/.*")
d = self._listSetup(lsub)
d.addCallback(self.assertListDelimiterAndMailboxAreStrings)
- d.addCallback(self.assertEqual, [])
+ d.addCallback(
+ self.assertEqual,
+ [
+ (SimpleMailbox.flags, "/", "ROOT/.SUBMATCH"),
+ (SimpleMailbox.flags, "/", "ROOT/.DEEP/MATCH"),
+ ],
+ )
return d
def testStatus(self):