Sep 18, 2026·6 min read·2 visits
A vulnerable unanchored regular expression in Soup Sieve's CSS parser allows unauthenticated remote attackers to trigger quadratic CPU exhaustion (ReDoS) and application denial of service by submitting a crafted CSS selector containing long runs of internal whitespace or comments. This issue is resolved in version 2.9.
A polynomial-time Regular Expression Denial of Service (ReDoS) vulnerability in Soup Sieve versions prior to 2.9 allows remote unauthenticated attackers to cause CPU exhaustion and thread-pool denial of service. The vulnerability resides in the trailing whitespace and comment preprocessing step of the CSS parser. An attacker can trigger quadratic backtracking by submitting a crafted CSS selector string containing a long run of internal spaces or comments terminated by a non-matching token. This blocks the Python Global Interpreter Lock (GIL) and halts worker threads.
The vulnerability designated as CVE-2026-85999 (also tracked under GHSA-j934-xhv5-fg8f) affects Soup Sieve, a CSS selector library designed for use with Beautiful Soup 4. The vulnerability is located within the preprocessing phase of CSS selector compilation inside soupsieve/css_parser.py. This component is responsible for parsing, tokenizing, and normalizing CSS queries before they are executed against structured HTML or XML trees.
An attacker who can influence the CSS selector queries processed by an application can exploit this vulnerability. The security flaw stems from an inefficient regular expression evaluation in the selector_iter function, which performs trailing trim operations on the selector patterns.
Because the trimming expression lacks a start anchor and is invoked using search methods, the Python regular expression engine evaluates candidate starting positions iteratively. This leads to excessive CPU cycles being expended when processing specific pattern distributions, degrading the availability of the host software.
The root cause is classified under CWE-1333: Inefficient Regular Expression Complexity and CWE-400: Uncontrolled Resource Consumption. Before tokenizing a CSS selector, the library executes a pre-processing step to strip leading and trailing whitespace and comments (WSC). The leading trim regular expression RE_WS_BEGIN is anchored with ^, ensuring linear evaluation.
However, the trailing trim regular expression, defined as RE_WS_END = re.compile(fr'{WSC}*$'), is evaluated without a start anchor. Because the .search() method is utilized, the backtracking engine of the Python re module attempts a match starting at every character offset from left to right.
When a CSS selector contains a long internal run of whitespace or comments followed by a non-matching character, the engine repeatedly attempts the greedy matching process. For a sequence of length $n$, the engine performs approximately $O(n^2)$ match operations. The failure of the end-of-string anchor $ at the terminating character forces the engine to shift by one index and retry, resulting in quadratic CPU consumption.
In vulnerable versions prior to 2.9, the css_parser.py preprocessing implementation uses the following code path:
# Vulnerable Implementation
RE_WS_BEGIN = re.compile(fr'^{WSC}*')
RE_WS_END = re.compile(fr'{WSC}*$')
def selector_iter(self, pattern: str) -> Iterator[tuple[str, Match[str]]]:
# Ignore whitespace and comments at start and end of pattern
m = RE_WS_BEGIN.search(pattern)
index = m.end(0) if m else 0
m = RE_WS_END.search(pattern) # Vulnerable unanchored search
end = (m.start(0) - 1) if m else (len(pattern) - 1)The official fix, merged in commit cf198fcddc9230f06ed39f974eba0ce076b85cda, redesigns the verification flow. The developers redefined the comment parsing structure and introduced string reversal during preprocessing to safely anchor the regular expression.
# Patched Implementation
RE_WS_END = re.compile(fr'^(?:[ \t]|(?:\n\r|(?!\n\r)[\n\f\r])|{COMMENTS})*')
def selector_iter(self, pattern: str) -> Iterator[tuple[str, Match[str]]]:
# Ignore whitespace and comments at start and end of pattern
m = RE_WS_BEGIN.search(pattern)
index = m.end(0) if m else 0
# The pattern is reversed to match trailing items using a start anchor
m = RE_WS_END.search(pattern[::-1])
offset = m.end(0) if m else 0
end = len(pattern) - (1 + offset)By reversing the pattern string and utilizing the start anchor ^ on the reversed text, the regular expression engine is constrained to match strictly from index 0. If a mismatch is encountered at the beginning of the reversed pattern, the parser fails immediately in $O(1)$ time, mitigating the backtracking loop.
An attack can be executed if a target web application exposes an endpoint that compiles dynamic, user-supplied CSS selectors. This is common in web scraping utilities, content filtering tools, and administrative interfaces that run structural queries.
The exploit payload requires three elements: a valid CSS element selector, a long sequence of internal whitespace characters or CSS comments, and a single trailing character that does not match the trailing rules. An example string is: 'div' + ' ' * 20000 + 'b'. Alternatively, an exploit can be crafted using repeating CSS comments: 'div' + '/*comment*/' * 2000 + 'b'.
When the backend engine parses this query, the process halts. Because Python utilizes a Global Interpreter Lock (GIL), the CPU-bound regular expression processing prevents concurrent application threads from executing, leading to denial of service.
The impact is classified as a denial of service (DoS) vulnerability. Although CVSS v3.1 assigns a Medium severity of 5.3 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L), the practical impact on Python service deployments is high.
In synchronous or multi-threaded Python application servers (e.g., gunicorn, uWSGI), a thread locked in a regular expression backtracking loop will hold the GIL continuously. This starvation blocks other threads in the same process from handling incoming requests. If an attacker submits a small number of concurrent requests matching the exploit signature, they can exhaust the available worker pools, bringing down the application.
Because this vulnerability occurs during the initial preprocessing stage of Soup Sieve, it requires minimal system knowledge to exploit. The attack is highly repeatable and requires no authentication.
Remediation requires upgrading the soupsieve package to version 2.9 or above, which replaces the unanchored search pattern with the reversed-string anchored match.
To upgrade the package using pip, execute:
pip install --upgrade soupsieveIf immediate dependency updates are not possible, developers should implement input validation limits. Restrict the character length of selectors accepted from untrusted sources to a conservative ceiling (e.g., 256 characters). Developers can also sanitize input strings by compressing consecutive whitespace and stripping comments before passing the selectors to Beautiful Soup APIs.
# Defensive sanitization wrapper
import re
def safe_select(soup, selector):
# Compress internal whitespace sequences and strip comment blocks
sanitized = re.sub(r'\s+', ' ', selector)
sanitized = re.sub(r'/\*.*?\*/', '', sanitized)
if len(sanitized) > 512:
raise ValueError("Selector length exceeds safe limit")
return soup.select(sanitized)CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L| Product | Affected Versions | Fixed Version |
|---|---|---|
soupsieve facelessuser | < 2.9 | 2.9 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-1333 |
| Attack Vector | Network |
| CVSS v3.1 Score | 5.3 |
| EPSS Score | Not Available |
| Impact | Denial of Service (DoS) |
| Exploit Status | poc |
| KEV Status | Not Listed |
The product uses a regular expression that can be made to run in quadratic time relative to the input size, allowing attackers to consume excessive CPU cycles.
Plate core HTML deserialization APIs parse supplied HTML strings in the active document. When an application passes untrusted or cross-user HTML to these APIs, certain HTML attributes can trigger browser behavior before the HTML is converted into editor nodes.
A regular expression denial of service (ReDoS) vulnerability in Soup Sieve prior to version 2.9 allows remote attackers to cause CPU exhaustion and service disruption. The issue lies within the definition of the IDENTIFIER and VALUE selector sub-patterns in the CSS parser component, which uses overlapping adjacent quantified groups. When parsing long, crafted, or unclosed CSS selectors, backtracking-based regular expression engines experience quadratic performance degradation. User-controlled selectors can reach this path through soupsieve.compile(), soupsieve.select(), or BeautifulSoup.select(), while applications using only hard-coded selectors are unaffected.
A protocol-level validation bypass in CoreDNS versions prior to 1.14.7 allows unauthenticated remote attackers to proxy unauthorized DNS UPDATE messages (Opcode 5) using modern alternative transport layers such as DoH, DoH3, DoQ, and gRPC. If upstream authoritative servers trust the CoreDNS server's source IP and do not enforce TSIG authentication, attackers can inject, alter, or delete DNS zone records, leading to potential zone takeover or traffic redirection.
A path traversal vulnerability exists in Grav CMS versions prior to 2.0.16. The flaw occurs within the file validation mechanisms of the MediaUploadTrait, enabling authenticated users with media management privileges to bypass sandbox limitations. This allows the deletion of arbitrary files on the filesystem, which can result in denial of service or remote code execution.
An unauthenticated directory traversal vulnerability exists in Grav CMS prior to version 2.0.15. Due to an insecure string-based containment check (str_starts_with) in the pre-boot static asset server, attackers can read files in sibling directories sharing a prefix with the configured asset path when plugin-asset-map.php is enabled.
CVE-2026-75828 is a critical stored cross-site scripting (XSS) vulnerability in the getgrav Grav CMS before version 2.0.15. The vulnerability resides in the detectXss() security filter mechanism, where parser-differential mismatches between the regular-expression-based server-side validation and browser HTML5 tokenization allow authenticated editors to bypass event-handler detection and inject arbitrary JavaScript execution vectors.