Skip to content

fix: guard against parser hang on selectors with unbalanced parentheses#603

Closed
ghost wants to merge 1 commit into
masterfrom
unknown repository
Closed

fix: guard against parser hang on selectors with unbalanced parentheses#603
ghost wants to merge 1 commit into
masterfrom
unknown repository

Conversation

@ghost
Copy link
Copy Markdown

@ghost ghost commented Mar 19, 2026

Summary

Prevents juice from hanging indefinitely when CSS contains modern pseudo-class selectors like :is() with comma-separated arguments.

Problem

Mensch (the CSS parser) incorrectly splits :is(.a, .b) at the comma into two fragments:

  1. :is(.a (unclosed parenthesis)
  2. .b) (unmatched close)

When slick's parser receives the malformed :is(.a fragment, it enters catastrophic backtracking and hangs indefinitely with 100% CPU usage.

Fix

Added a hasBalancedParentheses() guard in lib/selector.js that checks for unbalanced parentheses before passing selectors to slick. Selectors with unbalanced parens return an empty parse result (same as the existing error handling for invalid selectors).

Test results

Fixes #587

Skip selectors with unbalanced parentheses before passing them to
slick's parser. Mensch incorrectly splits pseudo-class selectors
like :is(.a, .b) at the comma, producing fragments like ':is(.a'
with unclosed parentheses. These malformed selectors cause
catastrophic backtracking in slick, hanging indefinitely with
100% CPU usage.

The guard checks parenthesis depth in O(n) and returns an empty
parse result for malformed selectors, matching the existing error
handling behavior.

Fixes #587
@ghost ghost closed this by deleting the head repository Mar 26, 2026
This pull request was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Parser hangs indefinitely on :is() pseudo-class with comma-separated selectors

1 participant