The Vulnerability at a Glance
| Field | Detail |
|---|---|
| Vulnerability | Denial of Service via Exponential-Time Brace Expansion |
| CWE | CWE-1333 – Inefficient Regular Expression Complexity |
| Language | JavaScript / Node.js |
| Risk | Attacker can freeze or crash the server process with a single string |
| Root Cause | O(2ⁿ) worst-case complexity for nested brace patterns in brace-expansion < 2.1.4 |
| Fix | Upgrade to brace-expansion 2.1.4 + minimatch 5.1.9; pin with npm overrides |
Introduction
The package-lock.json file is the unsung gatekeeper of your Node.js supply chain — it pins every transitive dependency your application relies on. In this project, that file locked brace-expansion at version 2.0.2, a version now known to contain CVE-2026-13149: a high-severity Denial of Service vulnerability caused by exponential-time algorithmic complexity.
brace-expansion is the library that turns strings like file.{js,ts,mjs} or src/{a,b,c}/{x,y} into their expanded equivalents. It sits inside minimatch, which in turn powers glob matching across a huge swath of the Node.js ecosystem — linters, test runners, build tools, and file watchers all depend on it. When an attacker can influence the string passed to this expansion logic, they can craft input that causes the algorithm to explode in size, grinding the process to a halt.
The Vulnerability Explained
How Brace Expansion Works — and Where It Breaks
Brace expansion is conceptually simple: {a,b}{c,d} expands to ['ac', 'ad', 'bc', 'bd']. The number of results is the product of the sizes of each brace group. That multiplicative relationship is exactly what makes it dangerous.
Consider a string like:
{a,b,c,d,e,f,g,h,i,j}{a,b,c,d,e,f,g,h,i,j}{a,b,c,d,e,f,g,h,i,j}...
Or, more insidiously, deeply nested patterns:
{{{{{{{{{{{a,b}}}}}}}}}}}}
In versions of brace-expansion prior to 2.1.4, the expansion algorithm does not guard against these combinatorial explosions. Each additional nesting level or additional comma-separated alternative multiplies the work required. With enough nesting, even a few hundred bytes of input can trigger millions of recursive calls and allocate gigabytes of intermediate strings.
The vulnerable entry in package-lock.json was:
"node_modules/brace-expansion": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
...
}
And minimatch at 5.1.6 depended directly on this vulnerable version via "brace-expansion": "^2.0.1".
A Concrete Attack Scenario
Suppose your application exposes an endpoint that accepts a glob pattern from the user to search for files or match routes:
const minimatch = require('minimatch');
app.get('/files', (req, res) => {
const pattern = req.query.pattern; // user-controlled input
const results = files.filter(f => minimatch(f, pattern));
res.json(results);
});
An attacker sends a single HTTP request:
GET /files?pattern={a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p}{a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p}{a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p}{a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p}
That single pattern expands to 16⁴ = 65,536 strings — and a few more brace groups pushes it into the millions. The Node.js event loop is single-threaded; while it grinds through this expansion, no other requests are processed. The server becomes unresponsive. This is a classic ReDoS-style attack applied to algorithmic expansion rather than regular expressions.
Even if your application does not directly expose glob matching to users, any dependency in your tree that passes user-influenced paths through minimatch or brace-expansion is a potential vector.
The Fix
What Changed in package-lock.json
The fix upgrades two packages:
Before:
"node_modules/brace-expansion": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="
}
"node_modules/minimatch": {
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="
}
After:
"node_modules/brace-expansion": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz",
"integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg=="
}
"node_modules/minimatch": {
"version": "5.1.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz",
"integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw=="
}
What Changed in package.json
A critical addition was made to package.json — the overrides field:
"overrides": {
"brace-expansion": "2.1.4",
"minimatch": "5.1.9"
}
This is the key insight of the fix. Simply upgrading brace-expansion in the top-level node_modules is not enough — other packages in the dependency tree may have their own pinned references to the vulnerable 2.0.2 version. The overrides field (introduced in npm 8.3) forces npm to resolve every occurrence of brace-expansion across the entire dependency graph to 2.1.4, regardless of what individual packages declare as their peer dependency range.
Without overrides, you might upgrade the direct dependency but leave transitive copies of the vulnerable version buried inside nested node_modules directories — a subtle but dangerous gap.
Why minimatch Was Also Updated
minimatch is the primary consumer of brace-expansion in this project's dependency tree. Upgrading minimatch from 5.1.6 to 5.1.9 ensures that the package itself is pulling in the patched brace-expansion, and that any internal behavior changes in minimatch that complement the brace-expansion security fix are also included.
What the Patch Actually Does Inside brace-expansion
The 2.1.4 release of brace-expansion introduces a result-count limit and depth guard inside the expansion algorithm. Before generating the full expanded set, the library now calculates the expected output size. If that size exceeds a safe threshold, the expansion is aborted or capped — preventing the exponential blowup entirely. Valid, reasonably-sized brace expressions continue to work exactly as before.
Prevention & Best Practices
1. Use npm overrides for Transitive Dependency Pinning
When a vulnerability exists in a transitive dependency (a dependency of a dependency), simply running npm update may not fix it. Use overrides in package.json to enforce a minimum safe version:
"overrides": {
"vulnerable-package": ">=safe-version"
}
For Yarn, use resolutions. For pnpm, use pnpm.overrides.
2. Validate and Sanitize User-Supplied Glob Patterns
If your application accepts glob patterns from users, apply strict validation before passing them to any expansion or matching library:
// Limit pattern length
if (pattern.length > 256) {
throw new Error('Pattern too long');
}
// Limit brace nesting depth and count
const braceCount = (pattern.match(/\{/g) || []).length;
if (braceCount > 5) {
throw new Error('Too many brace groups in pattern');
}
3. Run Automated Dependency Scanning in CI
Integrate tools like Trivy, npm audit, or Snyk into your CI pipeline. CVE-2026-13149 was detected by Trivy scanning package-lock.json. A pipeline gate that fails on HIGH or CRITICAL findings would have caught this before deployment.
Example GitHub Actions step:
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
exit-code: '1'
severity: 'HIGH,CRITICAL'
4. Keep package-lock.json in Version Control
Your package-lock.json is not just a build artifact — it is a security document. Committing it ensures that every developer and CI environment uses the exact same (and audited) dependency versions.
5. Understand the Scope of Algorithmic Complexity Attacks
Algorithmic complexity attacks (also called "algorithmic DoS" or "complexity attacks") are distinct from resource exhaustion through volume. A single, small HTTP request can cause unbounded CPU consumption. Standard rate limiting and WAF rules often miss these attacks because the payload is not large. Defense must happen at the library level — which is exactly what the brace-expansion patch provides.
Relevant standards:
- OWASP: Denial of Service Cheat Sheet
- CWE-1333: Inefficient Regular Expression Complexity
- CWE-400: Uncontrolled Resource Consumption
Key Takeaways
package-lock.jsonversion2.0.2ofbrace-expansionwas the exact vulnerable artifact — upgrading to2.1.4closes the CVE-2026-13149 attack surface entirely.- Transitive vulnerabilities require
overrides— simply upgrading a direct dependency is not enough when the vulnerable package appears deeper in the dependency tree viaminimatch's own resolution. - A single HTTP request carrying a crafted brace pattern can freeze a Node.js event loop — this is not a high-volume attack, making it especially dangerous and hard to catch with traditional rate limiting.
minimatch 5.1.9was upgraded alongsidebrace-expansion 2.1.4— both changes work together; updating only one may leave a residual risk path through the other.- Trivy's static scan of
package-lock.jsonwas sufficient to detect this — you do not need runtime instrumentation to find this class of vulnerability; dependency manifest scanning is enough.
How Orbis AppSec Detected This
- Source: The
brace-expansionlibrary processes strings that can originate from user-controlled input passed throughminimatch-based glob matching anywhere in the application or its dependencies. - Sink: The brace expansion algorithm inside
node_modules/brace-expansion(version2.0.2), invoked whenever a pattern string containing{characters is processed — effectively any call tominimatch(file, pattern)wherepatternis user-influenced. - Missing control: No upper bound on expansion result count or recursion depth was enforced before version
2.1.4; the algorithm would unconditionally attempt to materialize all combinations. - CWE: CWE-1333 – Inefficient Regular Expression Complexity (applicable to algorithmic expansion complexity as well as regex).
- Fix:
brace-expansionwas upgraded from2.0.2to2.1.4andminimatchfrom5.1.6to5.1.9, with npmoverridesadded topackage.jsonto enforce these versions across the entire dependency tree.
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-13149 is a reminder that security vulnerabilities are not always about memory corruption or injection attacks — sometimes the danger is purely mathematical. The brace-expansion library's O(2ⁿ) worst-case behavior for crafted inputs is a textbook algorithmic complexity vulnerability, and it sits inside one of the most widely used glob-matching stacks in the Node.js ecosystem.
The fix is precise and low-risk: upgrading brace-expansion to 2.1.4 and minimatch to 5.1.9, and using npm overrides to ensure the patched versions propagate through every layer of the dependency tree. Valid inputs continue to work exactly as before — only the malicious edge case is blocked.
The broader lesson is one of supply chain hygiene: your application's security posture is only as strong as its weakest transitive dependency. Automated scanning of package-lock.json and package.json — as demonstrated by Trivy's detection of this exact CVE — is an essential, low-friction control that every Node.js project should have in its CI pipeline.