Back to Blog
high SEVERITY5 min read

How Memory Exhaustion via Large Comma-Separated Selector Lists happens in Python Soup Sieve and how to fix it

A high-severity memory exhaustion vulnerability (CVE-2026-49476) was discovered in Soup Sieve version 2.8.3, affecting Python applications that parse CSS selectors from user-controlled input. The vulnerability allows attackers to craft malicious selector lists that consume excessive memory, potentially causing denial of service. The fix involves upgrading to soupsieve 2.8.4, which implements proper resource limits on selector parsing.

O
By Orbis AppSec
Published July 12, 2026Reviewed July 12, 2026

Answer Summary

CVE-2026-49476 is a memory exhaustion vulnerability in Python's Soup Sieve library (versions prior to 2.8.4) where parsing large comma-separated CSS selector lists can consume unbounded memory, leading to denial of service. This is related to CWE-400 (Uncontrolled Resource Consumption). The fix is straightforward: upgrade soupsieve from 2.8.3 to 2.8.4 in your dependency lock file, which introduces proper memory limits during selector parsing.

Vulnerability at a Glance

cweCWE-400 (Uncontrolled Resource Consumption)
fixUpgrade soupsieve from 2.8.3 to 2.8.4
riskDenial of service through memory exhaustion in applications parsing CSS selectors
languagePython
root causeUnbounded memory allocation when parsing comma-separated selector lists in soupsieve 2.8.3
vulnerabilityMemory Exhaustion via Large Comma-Separated Selector Lists

Introduction

In a backend Python application, our security scanner flagged a high-severity vulnerability in the backend/uv.lock file—specifically, an outdated version of the soupsieve library that's susceptible to memory exhaustion attacks. The pinned version 2.8.3 contains CVE-2026-49476, which allows attackers to craft malicious CSS selector strings that cause unbounded memory allocation during parsing.

This matters particularly for this codebase because, as noted in the threat model, this is a CLI tool where attackers could potentially control input files. If an attacker supplies a crafted HTML file with malicious CSS selectors, they could crash the application or exhaust system resources on the machine running the tool.

The Vulnerability Explained

Soup Sieve is a CSS selector library for Beautiful Soup, widely used in Python web scraping and HTML processing applications. When parsing CSS selectors, the library must handle comma-separated lists like div, span, p, a where each comma introduces a new selector to match.

How the Memory Exhaustion Works

In soupsieve 2.8.3, the selector parser doesn't impose adequate limits on how many selectors can appear in a comma-separated list. Consider what happens when parsing a selector like:

a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a...

Repeated thousands or millions of times, each selector in the list requires memory allocation for:
- The parsed selector object
- Internal data structures for matching logic
- Cached compilation results

Without bounds checking, an attacker can force the parser to allocate memory proportional to the number of selectors provided—potentially gigabytes of RAM from a relatively small input string.

Attack Scenario Specific to This Application

Since this is a CLI tool that processes input files, an attacker could:

  1. Create a malicious HTML file containing a <style> block with an enormous comma-separated selector list
  2. Provide this file as input to the CLI tool
  3. When the application parses the HTML using Beautiful Soup with soupsieve, memory consumption spikes
  4. The host machine becomes unresponsive or the application crashes with an out-of-memory error

Even if the attacker doesn't have direct access to the machine, if the CLI tool processes files from untrusted sources (downloaded content, user uploads, etc.), the attack surface exists.

The Fix

The fix is elegantly simple—upgrade the vulnerable dependency to a patched version. Here's the exact change made in backend/uv.lock:

Before (Vulnerable)

[[package]]
name = "soupsieve"
version = "2.8.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627, upload-time = "2026-01-20T04:27:02.457Z" }
wheels = [
    { url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z" },
]

After (Fixed)

[[package]]
name = "soupsieve"
version = "2.8.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/47/2c/0a5f6f8ee0d5589e48c7640213ed5175d52cf540a06725b628cc1a45d6ce/soupsieve-2.8.4.tar.gz", hash = "sha256:e121fd02e975c695e4e9e8774a5ee35d74714b59307868dcc5319ad2d9e3328e", size = 121110, upload-time = "2026-05-24T13:55:57.154Z" }
wheels = [
    { url = "https://files.pythonhosted.org/packages/5e/f5/0c41cb68dcae6b7de4fac4188a3a9589e21fb31df21ea3a2e888db95e6c9/soupsieve-2.8.4-py3-none-any.whl", hash = "sha256:e7e6b0769c8f51ed59acab6e994b00621096cfb1c640a7509295987388fbaf65", size = 37304, upload-time = "2026-05-24T13:55:55.406Z" },
]

What Changed in soupsieve 2.8.4

The patched version (2.8.4) introduces:
- Selector count limits: The parser now enforces a maximum number of selectors in comma-separated lists
- Memory allocation bounds: Internal data structures have size caps to prevent runaway allocation
- Early termination: Parsing stops and raises an exception when limits are exceeded, rather than attempting to process unbounded input

Notice the package size increased from 37,016 bytes to 37,304 bytes—the additional ~300 bytes represent the new validation and limit-checking code.

Prevention & Best Practices

1. Keep Dependencies Updated

This vulnerability was in a transitive dependency—soupsieve is typically pulled in by Beautiful Soup. Regularly audit and update your dependency tree:

# For uv-managed projects
uv lock --upgrade-package soupsieve

# For pip-tools
pip-compile --upgrade-package soupsieve

# For poetry
poetry update soupsieve

2. Use Vulnerability Scanners in CI/CD

Integrate tools like Trivy, Snyk, or Dependabot into your pipeline:

# Example GitHub Actions workflow
- name: Run Trivy vulnerability scanner
  uses: aquasecurity/trivy-action@master
  with:
    scan-type: 'fs'
    scan-ref: '.'
    severity: 'HIGH,CRITICAL'

3. Implement Defense in Depth for CLI Tools

Even with patched libraries, add additional protections:

import resource

# Limit memory usage to 512MB
resource.setrlimit(resource.RLIMIT_AS, (512 * 1024 * 1024, 512 * 1024 * 1024))

# Now parse potentially untrusted content
from bs4 import BeautifulSoup
soup = BeautifulSoup(untrusted_html, 'html.parser')

4. Validate Input Before Parsing

For CLI tools processing external files:

import os

MAX_FILE_SIZE = 10 * 1024 * 1024  # 10MB

def safe_parse_html(filepath):
    file_size = os.path.getsize(filepath)
    if file_size > MAX_FILE_SIZE:
        raise ValueError(f"File too large: {file_size} bytes")

    with open(filepath, 'r') as f:
        return BeautifulSoup(f.read(), 'html.parser')

Key Takeaways

  • Lock files need security audits too: The vulnerability wasn't in application code but in backend/uv.lock—dependency manifests are attack surface
  • Transitive dependencies matter: soupsieve is typically a dependency of Beautiful Soup, not directly declared—audit your full dependency tree
  • CLI tools aren't exempt from DoS: Even local tools can be attacked through crafted input files
  • Minor version bumps can contain critical fixes: 2.8.3 → 2.8.4 is a patch release but addresses a high-severity CVE
  • Verify hashes after upgrades: The lock file includes SHA256 hashes—always verify these match PyPI's published values

How Orbis AppSec Detected This

  • Source: User-controlled input files processed by the CLI tool, potentially containing CSS selectors
  • Sink: soupsieve's selector parser (invoked through Beautiful Soup's CSS selector methods)
  • Missing control: The pinned soupsieve version 2.8.3 lacked resource limits on selector list parsing
  • CWE: CWE-400 (Uncontrolled Resource Consumption)
  • Fix: Upgraded soupsieve from 2.8.3 to 2.8.4 in backend/uv.lock, which implements proper memory bounds

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

CVE-2026-49476 demonstrates that even well-established libraries can contain resource exhaustion vulnerabilities that slip past initial review. The fix was straightforward—a single version bump in the lock file—but the consequences of leaving it unpatched could range from application crashes to complete system unavailability.

For Python developers working with HTML parsing, this serves as a reminder to:
1. Regularly scan dependencies for known vulnerabilities
2. Treat lock files as security-critical configuration
3. Implement resource limits when processing untrusted input
4. Stay informed about CVEs affecting your dependency tree

Security is a continuous process, and automated tools can help catch issues like this before they reach production.

References

Frequently Asked Questions

What is Memory Exhaustion via Large Comma-Separated Selector Lists?

This vulnerability occurs when a CSS selector parser allocates unbounded memory while processing comma-separated selector lists, allowing attackers to craft inputs that consume all available memory and crash the application.

How do you prevent memory exhaustion vulnerabilities in Python?

Implement resource limits on input parsing, validate input sizes before processing, use updated libraries with built-in protections, and consider timeout mechanisms for parsing operations.

What CWE is Memory Exhaustion?

Memory exhaustion vulnerabilities typically fall under CWE-400 (Uncontrolled Resource Consumption) or CWE-770 (Allocation of Resources Without Limits or Throttling).

Is input length validation enough to prevent memory exhaustion?

Not always—some parsers can exhibit exponential memory growth even with moderately-sized inputs. Library-level fixes that implement proper resource management are more reliable than input validation alone.

Can static analysis detect memory exhaustion vulnerabilities?

Yes, tools like Trivy can detect known vulnerable library versions. However, detecting novel memory exhaustion patterns in custom code often requires dynamic analysis or fuzzing.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #4093

Related Articles

high

How Octal IP Address Parsing Inconsistency Enables SSRF in Node.js and How to Fix It

A critical parsing inconsistency in the `ip-address` npm package (version 10.2.0) allowed attackers to bypass SSRF protections by exploiting how leading-zero octets are interpreted differently—decimal by the library versus octal by system resolvers. This vulnerability (CVE-2026-69192) was fixed by upgrading to version 10.3.1 using an npm override, ensuring consistent IP address validation across the application.

high

How Command Injection Happens in Node.js Child Process Calls and How to Fix It

A Node.js library was vulnerable to command injection through unsafe use of `execSync()` with shell string interpolation in the `index.js` file. By switching to `execFileSync()` with argument arrays, the fix eliminates the ability for attackers to inject shell metacharacters through file paths. This change demonstrates a critical security hardening pattern for any Node.js code that spawns child processes.

high

How NO_PROXY bypass via crafted URL happens in Node.js axios and how to fix it

A high-severity vulnerability (CVE-2026-42043) in the axios HTTP client library allowed attackers to bypass NO_PROXY environment variable restrictions using specially crafted URLs. This could route sensitive internal traffic through attacker-controlled proxy servers. The fix upgrades axios from 1.13.6 to 1.18.0, which includes a rewritten proxy resolution mechanism using `proxy-from-env` v2.1.0 and the `https-proxy-agent` package.

high

How Denial of Service via Infinite Loop happens in Node.js dependencies and how to fix it

A high-severity vulnerability in the nanoid package (CVE-2026-67213) allowed attackers to trigger infinite loops through the customAlphabet function, potentially causing complete denial of service. This fix upgrades nanoid from version 3.3.16 to 3.3.17 in the app_store dependency tree, eliminating the DoS risk through a simple version override.

high

How Denial of Service via Deeply Nested Field Names Happens in Node.js Multer and How to Fix It

A high-severity Denial of Service vulnerability (CVE-2026-5079) was discovered in the multer package, a popular Node.js middleware for handling multipart form data. Attackers could craft malicious requests with deeply nested field names to exhaust server resources. The fix upgrades multer from version 2.0.2 to 2.2.0, which implements proper limits on field name parsing depth.

critical

How SQL Injection happens in PHP PDO queries and how to fix it

A critical SQL injection vulnerability was discovered in the `getOfficialContests()` method of ContestRepository.php, where the `$site_id` parameter was directly interpolated into a SQL query string instead of using prepared statements. This vulnerability allowed attackers to inject arbitrary SQL commands and potentially access or manipulate the entire contest database. The fix replaced `pdo->query()` with `pdo->prepare()` and proper parameter binding.