Skip to content

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

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

⬆️ Updates soupsieve to v2.8.4 [SECURITY]#3566
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.8.4 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).


Release Notes

facelessuser/soupsieve (soupsieve)

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.

Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
@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 👍

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