fix: possible DoS with bad search patterns
Massimo Melina committed
Apr 9, 2026 at 21:04 UTC
87e2b8cbc2b07bfd08bfc47907fc5cf6d8c29e95
2 files changed
+8
-4
src/cross.ts
+7
-3
@@ -486,9 +486,13 @@ export function runAt(ts: number, cb: Callback) {
486
}
487
}
488
489
-export function makeMatcher(mask: string, emptyMaskReturns=false) {
490
- return mask ? picomatch(mask.replace(/^(!)?/, '$1(') + ')', { nocase: true}) // adding () will allow us to use the pipe at root level
491
- : () => emptyMaskReturns
489
+export function makeMatcher(mask: string, emptyMaskReturns=false, extglobs=true) {
490
+ if (!mask) return () => emptyMaskReturns
491
+ const wrapped = mask.replace(/^(!)?/, '$1(') + ')' // adding () will allow us to use the pipe at root level
492
+ const opts = { nocase: true, noextglob: !extglobs }
493
+ // reject patterns that compile to nested quantified groups, causing catastrophic backtracking (CVE-2026-33671)
494
+ return /\)\)[+*]/.test(picomatch.makeRe(wrapped, opts).source) ? () => false
495
+ : picomatch(wrapped, opts)
496
}
497
498
// this is caching all matchers, so don't use it with frequently changing masks. Benchmarks revealed that _.memoize make it slower than not using it, while this simple cache can speed up to 30x
src/misc.ts
+1
-1
@@ -18,7 +18,7 @@ import _ from 'lodash'
18
19
export function pattern2filter(pattern: string){
20
const matcher = makeMatcher(pattern.includes('*') ? pattern // if you specify *, we'll respect its position
21
- : pattern.split('|').map(x => `*${x}*`).join('|'))
21
+ : pattern.split('|').map(x => `*${x}*`).join('|'), false, false)
22
return (s: string) =>
23
!pattern || matcher(basename(s||''))
24
}