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 missing Dependabot cooldown happens in GitHub Actions and how to fix it

A high-severity configuration vulnerability was discovered in a `.github/dependabot.yml` file that lacked a cooldown period for package updates. Without this safeguard, Dependabot could immediately propose updates to newly published package versions—including potentially malicious or unstable releases. The fix adds a simple `cooldown` block with a 7-day waiting period before any new package version is suggested.

high

How Server-Sent Events Injection via Unsanitized Newlines happens in Node.js h3 and how to fix it

A high-severity Server-Sent Events (SSE) injection vulnerability (CVE-2026-33128) was discovered in the h3 HTTP framework, where unsanitized newline characters in event stream fields could allow attackers to inject arbitrary SSE messages. The fix upgrades h3 from version 1.15.5 to 1.15.6 in the frontend's dependency tree, ensuring that newline characters are properly sanitized before being written to event streams.

high

How prototype pollution via `__proto__` key happens in Node.js defu and how to fix it

A high-severity prototype pollution vulnerability (CVE-2026-35209) was discovered in the `defu` package version 6.1.4, which allowed attackers to inject properties into JavaScript's `Object.prototype` via the `__proto__` key in defaults arguments. The fix upgrades `defu` to version 6.1.5 in the frontend's dependency tree, protecting downstream consumers like `c12` and `dotenv` configuration loaders from malicious property injection.

critical

How buffer overflow in memcpy() happens in Node.js N-API bindings and how to fix it

A critical buffer overflow vulnerability was discovered in the GetBufferAsVector() function in examples_nodejs/src/zupt_napi.cpp, where memcpy() copied data from JavaScript Uint8Array buffers without proper bounds validation. This vulnerability could allow attackers to trigger memory corruption by providing maliciously crafted input arrays to the native Node.js module, potentially leading to crashes or arbitrary code execution.

high

How memory exhaustion via large comma-separated selector lists happens in Python soupsieve and how to fix it

A high-severity memory exhaustion vulnerability (CVE-2026-49476) was discovered in soupsieve 2.8.3, a CSS selector library used by BeautifulSoup in Python. An attacker who could influence CSS selector input could craft large comma-separated selector lists to exhaust system memory, causing denial of service. The fix upgrades soupsieve from 2.8.3 to 2.8.4 in the backend's `uv.lock` dependency file.

high

How buffer overflow from unsafe string copy functions happens in C network interface code and how to fix it

A high-severity buffer overflow vulnerability was discovered in `generic/eth-impl.c`, where unsafe `strncpy()` and `sprintf()` calls could write beyond buffer boundaries when handling network interface names and device filenames. The fix replaced these dangerous functions with bounded `snprintf()` calls that guarantee null-termination and prevent memory corruption.