Skip to content

fix: detect glob-star-in-branch and repetition overlap as ReDoS-risky extglobs (CVE-2026-112366) - #235

Closed
ShlomoRussell wants to merge 1 commit into
micromatch:masterfrom
ShlomoRussell:chore-fix-extglob-redos
Closed

ShlomoRussell wants to merge 1 commit into
micromatch:masterfrom
ShlomoRussell:chore-fix-extglob-redos

Conversation

@ShlomoRussell

Copy link
Copy Markdown

Problem

CVE-2026-112366 / SNYK-JS-PICOMATCH-21248802 (CVSS 8.7 High): Patterns
like +(*), +(*a), +(a*), and *(a*|b) trigger exponential regex
backtracking, blocking the Node.js event loop with inputs as short as
25-30 characters.

This also addresses the multi-character prefix overlap bypass reported
in #175 — patterns like +(ab|abab) that escaped the existing
hasRepeatedCharPrefixOverlap safeguard.

Root cause

analyzeRepeatedExtglob had two blind spots:

  1. Glob stars in branches — A glob * inside a +() or *()
    branch compiles to [^/]*?, creating a nested quantifier
    ((?:[^/]*?X)+) with exponential partition ambiguity. The existing
    checks only caught wildcard-only branches in multi-branch patterns.

  2. Multi-char repetition overlap — The existing
    hasRepeatedCharPrefixOverlap only detected single-character
    repeats (e.g., a|aa). Multi-character cases like ab|abab — where
    one branch is a whole-string repetition of another — were missed.

Fix

  • branchContainsGlobStar: detects unescaped * outside bracket
    classes, distinguishing glob stars (*x) from extglob quantifiers
    (*(x)) via branch[i+1] !== '('.
  • hasBranchRepetitionOverlap: replaces the single-char-only check
    with a general "is one branch a repetition of another" test.
  • Both checks run early in analyzeRepeatedExtglob, before the existing
    star-sequence and recursion analysis. The maxExtglobRecursion: false
    escape hatch continues to disable all safeguards.

Common patterns like +(js|jsx), +(foo|bar), *(foo|bar), and
*.+(js|jsx) are not affected — the fix correctly identifies that
jsx is not a repetition of js.

Evidence

Before fix (+(*a) against 'a'.repeat(30)+'!'): 8,744 ms
After fix: <0.1 ms

Before fix (+(ab|abab) against 'ab'.repeat(40)+'c'): 1,779 ms
After fix: <0.1 ms

Full test suite: 1986 passing, 0 failing.

Test changes

  • 4 existing assertions updated: they previously asserted that
    vulnerable patterns like +(*)c and *(a*|b) would match
    functionally. They now assert literalized behavior.
  • 3 new test blocks: CVE-specific regression tests, repetition-overlap
    tests (including positive tests for safe patterns), and
    extglob-quantifier-vs-glob-star distinction tests.

Credit

Vulnerability reported by maximilliangrand and Zachariah Mithani (Snyk
advisory). Multi-char overlap bypass reported by Raunak Gupta (#175).

Fixes #175

…ReDoS-risky extglobs

Addresses CVE-2026-112366 (SNYK-JS-PICOMATCH-21248802) and the bypass
reported in micromatch#175.

Two classes of pattern escaped the existing `analyzeRepeatedExtglob`
safeguard and produced regexes with catastrophic backtracking:

1. Single-branch (or any branch) containing a glob star, e.g. `+(*)`,
   `+(*a)`, `+(a*)`, `*(a*|b)`. The glob `*` compiles to `[^/]*?`,
   creating a nested quantifier inside the outer `+`/`*` group.

2. Multi-branch alternations where one branch is a repetition of
   another, e.g. `+(ab|abab)`, `+(abc|abcabc)`. The repeated-substring
   relationship creates exponential partition ambiguity.

Fix:
- Add `branchContainsGlobStar` — detects unescaped `*` outside bracket
  classes, distinguishing glob stars (`*x`) from extglob quantifiers
  (`*(x)`) by checking `branch[i+1] !== '('`.
- Replace `hasRepeatedCharPrefixOverlap` (only caught single-char
  repeats like `a|aa`) with `hasBranchRepetitionOverlap` (catches any
  `short|short.repeat(k)` pair, e.g. `ab|abab`).
- Both checks run before the existing star-sequence and recursion
  analysis, so the `maxExtglobRecursion: false` escape hatch still
  disables all safeguards for trusted patterns.

Patterns like `+(js|jsx)` and `+(foo|foobar)` are correctly preserved
as functional regexes — the shorter branch is not a divisor of the
longer one, so no exponential partitioning occurs.

Credit: maximilliangrand and Zachariah Mithani (Snyk advisory reporters),
Raunak Gupta (micromatch#175 reporter).
@Re1nGer

Re1nGer commented Oct 21, 2026

Copy link
Copy Markdown

Any timeline on when it will be merged?
Thanks

@jonschlinkert

Copy link
Copy Markdown
Member

I might disable PRs permanently due to the volume of AI generated fake vulnerabilities. How many times do I have to explain that globs are regular expressions. The general star height problem, which has not been solved, describes why we cannot predict all patterns like this. It's not a vulnerability, it's nonsense.

My recommendation to anyone who thinks this is a problem:

  1. Don't use that pattern.
  2. Don't let untrusted users define regular expressions

@ShlomoRussell

Copy link
Copy Markdown
Author

I might disable PRs permanently due to the volume of AI generated fake vulnerabilities. How many times do I have to explain that globs are regular expressions. The general star height problem, which has not been solved, describes why we cannot predict all patterns like this. It's not a vulnerability, it's nonsense.

My recommendation to anyone who thinks this is a problem:

  1. Don't use that pattern.
  2. Don't let untrusted users define regular expressions

@jonschlinkert The CI at my job flagged this as a Snyk vulnerability that was blocking the builds. We had to request a waiver for the CI to pass. https://security.snyk.io/vuln/SNYK-JS-PICOMATCH-21248802. If it's really not a vuln, how can we get the CVE taken down?

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.

Security Audit: picomatch v4.0.4 | Incomplete ReDoS Fix Bypass (P1)

3 participants