BREW-SCRAPY-CVE-2026-106454 (GHSA-8PQF-F4M5-798G)

Vulnerability from osv_homebrew – Published: 2026-10-08 11:17 – Updated: 2026-10-08 11:17 – Source website
VLAI
Summary
Twisted: IMAP wildcardToRegexp() ReDoS
Details

Summary

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:

  1. CREATE a mailbox whose name is an exponential-blowup trigger string.
  2. Issue LIST "" "(a+)+z" (or equivalent ReDoS pattern).
  3. 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": "scrapy",
        "purl": "pkg:brew/scrapy"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "2.9.0"
            }
          ],
          "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-scrapy-CVE-2026-106454",
  "modified": "2026-10-08T11:17:36Z",
  "published": "2026-10-08T11:17:36Z",
  "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"
  ]
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

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.

Loading…

Loading…

Loading…

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.


Loading…