Affected Versions
| Affected | < 2.3.2 (2.x line), < 3.0.2 (3.x line), < 4.0.4 (4.x line) |
| Fixed in | 2.3.2, 3.0.2, 4.0.4 |
| Ecosystem | npm |
| CVE / GHSA | CVE-2026-33671 / not assigned |
| CWE | unknown |
When Glob Matching Becomes a Denial of Service
picomatch is the glob matching engine behind @rollup/pluginutils, fast-glob, chokidar, and countless build tools. It compiles glob patterns into regular expressions for efficient matching. The vulnerability lies in how picomatch 2.3.1 handles extglob patterns—the Bash-style extended globs like @(pattern-list) for matching one of several alternatives.
When an extglob contains nested alternations combined with quantifiers, the generated regular expression can exhibit catastrophic backtracking. A pattern as simple as @(a|a)* causes the regex engine to explore exponentially many matching paths. On Node.js, this blocks the event loop entirely. No I/O completes. No timers fire. The process appears to hang until killed.
The danger is pervasive because picomatch often processes untrusted input. Build tools accept glob patterns from configuration files. File watchers accept patterns from user preferences. Any service that lets users specify which files to include or exclude is a potential target.
The Vulnerability Explained
The root cause is in picomatch's makeRe() function, which compiles glob patterns to regular expressions without sufficient normalization of extglob structures. Consider this vulnerable code path:
const picomatch = require('picomatch');
// User-controlled input from HTTP request, config file, CLI arg
const userPattern = '@(a|a)*'.repeat(20); // 20 repetitions of the toxic pattern
// This call blocks the event loop indefinitely
const isMatch = picomatch(userPattern);
The @(a|a) extglob creates a regex alternation (?:a|a). Combined with the * quantifier and repeated, the backtracking search space explodes. The regex engine tries every possible way to group the matches, and with no early bailout, CPU usage hits 100% with no progress.
In production systems, this manifests as:
- Build servers that hang on malicious package.json glob configurations
- File watchers that freeze when processing user-supplied ignore patterns
- CI pipelines that timeout after hours of CPU burn on a single glob evaluation
The attack is low complexity, high impact: a single 200-character string can disable a Node.js process for minutes to hours.
The Fix
The remediation upgrades picomatch from 2.3.1 to 4.0.4 (or the appropriate backport). The key changes in the dependency resolution:
// package.json - forcing patched version via overrides
{
"overrides": {
"@rollup/pluginutils": {
"picomatch": "4.0.4"
}
}
}
// package-lock.json version resolution
- "version": "2.3.1",
- "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz",
- "engines": {
- "node": ">=8.6"
- }
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "engines": {
+ "node": ">=12"
+ }
The fix involves two critical improvements in picomatch 4.0.4:
-
Pattern normalization: Extglob patterns are now pre-processed to eliminate redundant alternations like
(a|a)before regex compilation. This removes the structural cause of exponential backtracking. -
Regex hardening: The generated regular expressions include possessive quantifiers and atomic groups where supported, preventing the regex engine from backtracking into already-matched portions of the pattern.
The engine requirement bump from >=8.6 to >=12 reflects the use of modern JavaScript features in the patch, including better regex capabilities and performance optimizations.
Key Takeaways
-
Extglob patterns are regex injection vectors: Any code that accepts
@(...),*(...),+(...),?(...), or!(...)patterns from untrusted sources must treat them as potentially malicious input. -
The
overridesfield is essential for transitive dependencies: When a vulnerable package appears deep in the dependency tree (as picomatch does via@rollup/pluginutils),npm overridesforces the patched version without waiting for every intermediate package to update. -
Node.js engine requirements are security boundaries: The
>=12requirement in picomatch 4.0.4 isn't arbitrary—older Node versions lack the regex features and performance characteristics needed for a robust fix. Running EOL Node versions blocks security patches. -
Glob compilation must have timeouts: Applications processing user-supplied globs should wrap
picomatch()calls inAbortController-based timeouts or move compilation to worker threads to prevent event loop blocking.
How Orbis AppSec Detected This
Source: The pattern parameter passed to picomatch() or picomatch.makeRe() from user-controlled configuration, HTTP requests, or CLI arguments.
Sink: The internal regex compilation within picomatch's makeRe() function, specifically the path that processes extglob syntax into JavaScript RegExp objects.
Missing control: No validation of extglob complexity, no regex compilation timeouts, and no normalization of redundant alternation patterns before regex generation.
CWE: unknown (ReDoS patterns typically map to CWE-1333, but no official assignment was made for this CVE).
Fix: Force upgrade to picomatch 2.3.2, 3.0.2, or 4.0.4 via direct dependency update or npm overrides, ensuring the patched pattern normalization logic prevents catastrophic backtracking.
Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.
Conclusion
CVE-2026-33671 demonstrates how even "safe" pattern-matching libraries can become denial-of-service weapons when regex compilation meets untrusted input. The picomatch upgrade to 4.0.4 eliminates the backtracking vulnerability through structural pattern normalization, but the broader lesson applies to any code that compiles user input into regular expressions. Use npm overrides to force security fixes in transitive dependencies, validate pattern complexity before compilation, and always consider the regex engine's worst-case behavior when accepting untrusted glob expressions.