Skip to content
CVE-2026-121577 ·
Medium · November 3, 2026

Unescaped header separators in whatstk build a regular expression that backtracks exponentially

S0
Secur0 CNA
CVE-2026-121577

Description

Inefficient Regular Expression Complexity (CWE-1333) in generate_regex() (whatstk/whatsapp/parser.py) in whatstk from 0.2.0 before 0.9.0. A party who supplies a chat export can make the process that parses it spend an unbounded amount of CPU on a file of a few hundred bytes.

The function builds the header regular expression by replacing each %x code with its named capture group, and leaves every other character of the header format in the pattern unescaped. A date separator that is a regular expression metacharacter therefore alters the pattern instead of matching itself. A separator of + quantifies the capture group before it, giving (?P<day>\d{1,2})+(?P<month>\d{1,2})+(?P<year>\d{2,4}), and a run of digits that cannot complete the header makes the engine try every way of partitioning that run.

Header auto detection is enabled by default and derives the format from the text of the file, so the separator comes from the export itself and no header format has to be passed by the caller. A chat whose dates read 11+11+2011 is detected as %d+%m+%y, %H:%M - %name: and produces the quantified pattern on its own.

The same missing escape breaks other separators outright. (, ) and \ raise re.PatternError, *, | and ? raise TypeError, and ^ and $ raise KeyError. None of them is caught, so the exception surfaces in the calling program.

Vulnerability type (CWE)

  • CWE-1333: Inefficient Regular Expression Complexity

Affected versions

whatstk from 0.2.0 before 0.9.0. The unescaped substitution in generate_regex() dates from 0.1.10, but header auto detection arrives in 0.2.0 and is on by default from that release, which is the point at which the pattern can be driven by a chat file rather than by a format the caller chose. Default status: unaffected.

Measured on 0.8.1 from PyPI with default arguments and a file holding three ordinary lines plus one long run of digits: 16 digits in a 115 byte file took 0.011s, 20 digits 0.063s, 24 digits 0.486s, 28 digits 4.912s, and 32 digits in a 131 byte file took 40.811s, roughly tenfold per four digits. The same inputs on 0.9.0 take 0.010s or less, including at 48 digits, and parse correctly. All ten metacharacter separators tested parse normally on 0.9.0.

Score (CVSS 4.0)

Medium (6.7)

CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:A/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N

The attack vector is Local because the library reads an export file on the machine that runs it. Privileges required are None, since the party who supplies the file needs no access to that machine. Attack requirements are None because the attacker writes the whole export, separator included, and nothing has to coincide. User interaction is Active because the parse has to be invoked on the file. Confidentiality and integrity are None: nothing is read or altered. Availability on the vulnerable system is High because the parse does not complete, and at the growth rate measured a slightly longer run of digits in the same small file costs hours rather than seconds, while the metacharacter variants end the calling program with an uncaught exception instead. The subsequent system metrics are None, since what stops is the program doing the parsing.

A service that accepts uploaded exports and parses them with default arguments is the case where this matters most, and there the vector would be Network rather than Local.

Solution

Upgrade to whatstk 0.9.0 or later.

In an affected version, passing an explicit hformat that contains no regular expression metacharacters avoids the pattern being altered, but it does not help where the format is detected from the file, which is the default.

Patch

Released in 0.9.0, through pull request #181 and the release commit 01d66d8. generate_regex() now splits the header format on the %x codes, passes everything between them through re.escape, and joins the parts, so a separator that is a metacharacter matches itself. The same change corrects a matching error that was not part of the report: . was unescaped too, so a format of %d.%m.%y matched 06X08X2016 on 0.8.1.

Credits

  • m.gongora.carriedo - finder
  • Xoán M. Otero Jorge - analyst
  • Darío Rivas - analyst
  • Secur0 CNA - coordinator
  • Lucas Rodés-Guirao - remediation developer

Discovery source: External

Official record: CVE-2026-121577

Related records

  • CVE-2026-121564 - silent loss of messages when merging overlapping exports in the same library