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 t

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