CVE-2026-49476: soupsieve Memory Exhaustion via Unbounded Selector List
Passing a crafted CSS selector string with hundreds of thousands of comma-separated entries to soupsieve causes it to allocate hundreds of megabytes of memory, crashing or killing any Python web app t
The problem
The CSS parser in soupsieve imposes no limit on the number of comma-separated selectors in a single selector string. Every entry is fully parsed into a rich `Selector` object graph and stored in a `SelectorList`, consuming roughly 976 bytes of heap per item.
A 500 KB input of 250,000 repetitions of `a,` causes approximately 244 MB of heap allocation, a 488x amplification. Any server-side app that passes user input to `soupsieve.compile()`, `soup.select()`, or `soup.select_one()` is exposed, with no authentication required.
Proof of concept
A working proof-of-concept for CVE-2026-49476 in soupsieve, with the exact payload below.
import soupsieve as sv
# 500 KB selector string -> ~244 MB heap allocation
count = 250_000
selector = ",".join("a" for _ in range(count))
# triggers unbounded allocation in css_parser.py parse_selectors()
compiled = sv.compile(selector)
print(len(compiled.selectors)) # 250000The root cause is CWE-770 (Allocation of Resources Without Limits or Throttling). In `soupsieve/css_parser.py`, `parse_selectors()` iterates every comma-delimited segment and builds a full `_Selector` object for each one with no cap on list length. The 2.8.4 patch adds a hard upper bound on the number of selectors that `parse_selectors()` will accept, raising `SelectorSyntaxError` when the count exceeds the limit before allocating further objects.
The amplification is severe because a single ASCII character like `a` expands into a complex object graph including `SelectorList`, `Selector`, tag metadata, and associated structures, each costing ~976 bytes, so a compact repeated payload produces disproportionate memory growth.
The fix
Upgrade to soupsieve 2.8.4 (`pip install -U soupsieve`). The fix adds a selector-count ceiling in `parse_selectors()` inside `css_parser.py`, rejecting oversized selector lists before they can allocate unbounded memory. If you cannot upgrade immediately, validate and reject user-supplied selector strings that exceed a safe length threshold at the application layer before passing them to soupsieve.
Reported by Liyi Zhou, Ziyue Wang, Strick, Maurice, Chenchen Yu (University of Sydney Security Research Team).
Related research
- high · 7.5CVE-2026-49477CVE-2026-49477: soupsieve Regular Expression Denial of Service (ReDoS)
- high · 7.7phantom-audio: Arbitrary File Write and Decode-Bomb DoS via Unconfined MCP Tool Paths
- high · 7.5CVE-2026-48809CVE-2026-48809: python-engineio Unauthenticated Memory Exhaustion (DoS)
- high · 8.3CVE-2026-49471CVE-2026-49471: serena-agent Unauthenticated Flask Dashboard Enables DNS Rebinding to RCE