high · 7.5CVE-2026-49476Jul 9, 2026

CVE-2026-49476: soupsieve Memory Exhaustion via Unbounded Selector List

Shubham Kandhare
Security Engagement Manager, SecureLayer7

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…

Packagesoupsieve
Ecosystempip
Affected<= 2.8.3
Fixed in2.8.4

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.

python
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))  # 250000

The 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).

References: [1][2]

Related research