GHSA-4j63-6c4x-wfg4: Soup Sieve: General sibling combinator inside :has() matches untrusted HTML in quadratic time

  • Severity: MEDIUM
  • CVE: CVE-2026-126192
  • Ecosystems: pip
  • Packages: soupsieve

## Summary

Matching a selector that puts a general-sibling combinator inside `:has()`, such as `div:has(a ~ b)`, against untrusted HTML runs in O(n²) time in the number of sibling elements. A single small document with a few thousand siblings ties the process up for a minute of CPU, so an application that runs a fixed selector of this shape over HTML it doesn’t control can be stalled by one request.

`:has` is powerful, but much in the same way poor regular expressions can be crafted to cause catastrophic backtracking with greedy capturing, has can be used to create non-ideal scenarios that can cause large portions of the tree to be evaluated for one or many elements if not used thoughtfully.

This behavior is not exclusive to Soup Sieve, but browsers make note of these potential performance issues themselves: https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/:has#performance_considerations.

With all of that said, there is room for improvements that can in some cases greatly improve performance.

## Details

`:has(a ~ b)` asks “does this element contain an `a` that has a later sibling `b`?”. When no `b` is present, soupsieve can’t short-circuit, and it answers the question with a nested scan: `match_future_child` walks every candidate `a` under the scope, and for each one the general-sibling branch of `match_future_relations` walks all of that `a`’s following siblings looking for `b`. With n siblings that is (n-1) + (n-2) + … + 1 comparisons, i.e. O(n²)…

Source: GitHub Security Advisories · 2026-11-13