Stack Exhaustion in brace-expansion: CVE-2026-102276
What was fixed
The brace-expansion library, which powers glob pattern matching and string expansion across the Node.js ecosystem, contained a critical stack exhaustion vulnerability. Attackers could supply carefully crafted brace patterns—such as deeply nested or recursive braces—that would cause the parser to recurse unbounded, exhausting the V8 JavaScript engine's call stack and crashing the process. The vulnerability is now patched in multiple version lines: 5.0.12, 3.0.7, 2.1.5, and 1.1.19.
Affected Versions
| Affected | < 5.0.12, < 3.0.7, < 2.1.5, < 1.1.19 (depending on major version) |
| Fixed in | 5.0.12, 3.0.7, 2.1.5, 1.1.19 |
| Ecosystem | npm |
| CVE / GHSA | CVE-2026-102276 |
| CWE | unknown |
If you depend on brace-expansion indirectly through minimatch, glob, or other packages, you likely inherited one of these versions. Verify your exact dependency by checking package-lock.json or running npm list brace-expansion.
The Vulnerability Explained
The brace-expansion library parses brace patterns like {a,b,c} and expands them into separate strings: ["a", "b", "c"]. This is a core utility for glob matching, used by build tools, test runners, and file processors across the Node.js ecosystem.
The vulnerability lies in how the library handled nested braces. When expanding a pattern, the parser would recursively call itself to handle sub-patterns. However, there was no limit on recursion depth. An attacker could exploit this by supplying a pathological pattern:
// Example: deeply nested braces
"{a,{b,{c,{d,{e,{f,{g,{h,{i,{j,{k,{l,{m,{n,{o,{p,{q,{r,{s,{t,{u,{v,{w,{x,{y,{z}}}}}}}}}}}}}}}}}}}}}}}}}}"
Each layer of nesting causes a new recursive call. With enough depth—or a more sophisticated pattern crafted to trigger the worst-case recursion path—the call stack would overflow:
RangeError: Maximum call stack size exceeded
The Node.js process would crash immediately, unable to recover or handle the exception gracefully.
Real-world impact
Consider a web service that accepts a pattern parameter and expands it to customize build output or file matching:
// Example: a vulnerable API endpoint
app.get('/expand', (req, res) => {
const pattern = req.query.pattern; // User-controlled input
const expanded = require('brace-expansion')(pattern);
res.json({ result: expanded });
});
An attacker could send a request with a deeply nested brace pattern:
GET /expand?pattern={a,{b,{c,...(repeat 1000 times)...}}}
The service would attempt to expand the pattern, recurse until the stack was exhausted, and crash. The process would be unavailable until restarted. If not load-balanced, this is a complete denial of service.
The Fix
The patched versions enforce a maximum recursion depth during brace expansion. When the parser encounters a recursion depth exceeding a safe threshold (typically a few hundred levels), it stops and either returns the pattern as-is or throws a controlled error, rather than crashing the process.
The version upgrade from 5.0.9 to 5.0.12 (and corresponding upgrades in the 1.1.x, 2.1.x, and 3.0.x lines) embeds this depth-limit check:
// Before (vulnerable): no depth check
function expand(pattern, options) {
// ... parse and recurse without limit ...
}
// After (fixed): enforces depth limit
function expand(pattern, options) {
const maxDepth = options.maxDepth || 1000; // or similar limit
return expandWithDepth(pattern, 0, maxDepth);
}
function expandWithDepth(pattern, currentDepth, maxDepth) {
if (currentDepth > maxDepth) {
throw new Error('Brace expansion depth limit exceeded');
}
// ... parse and recurse, incrementing currentDepth ...
}
This prevents the unbounded recursion that led to stack exhaustion.
Why multiple version bumps?
The fix was released across four separate version lines because brace-expansion is used by multiple major versions of dependent packages:
- brace-expansion 1.1.x is used by older minimatch releases (v7 and earlier)
- brace-expansion 2.1.x is used by minimatch v8
- brace-expansion 3.0.x serves some specialized consumers
- brace-expansion 5.0.x is used by minimatch v9
Each major version of minimatch has its own dependency constraint on brace-expansion (e.g., ^2.1.0 or ^5.0.0). To ensure all users receive the fix without forcing unnecessary major-version upgrades, the maintainers patched all active lines. Upgrading your project simply requires running npm update, which will pull the patched version within your existing dependency constraint.
Key Takeaways
-
Recursion depth is not a free resource. Even simple recursive algorithms can become exploitable DoS vectors if an attacker can control the input depth. Always enforce a maximum recursion depth in parsers and expansion routines.
-
Unbounded recursion is silent until it fails catastrophically. Unlike memory leaks or resource exhaustion that might be caught in testing, stack overflow can occur suddenly on production data, and the process terminates immediately. Depth limits should be defensive defaults, not reactive fixes.
-
Transitive dependencies matter for availability. brace-expansion is used by glob, minimatch, and dozens of downstream libraries. A crash in a transitive dependency can bring down your entire application. Regularly audit your dependency tree (e.g.,
npm audit) and subscribe to security updates. -
Version-specific patches preserve compatibility. By releasing the fix across all active major versions, the maintainers ensured that users could upgrade without forcing a simultaneous migration of dependent packages. This is a best practice for widely-used utilities.
-
Validate input depth in user-facing APIs. If your code expands patterns, globs, or braces from user input, add your own depth validation before passing to the library. A simple length check or nesting-level counter can catch pathological inputs early.
How Orbis AppSec Detected This
Source: The brace-expansion library entry point, which accepts untrusted pattern strings from any caller.
Sink: The recursive expansion function that processes nested brace sub-patterns without a depth limit.
Missing control: No maximum recursion depth parameter or check; the algorithm could recurse indefinitely as long as the input contained nested braces.
CWE: unknown (but commonly mapped to CWE-674: Uncontrolled Recursion).
Fix: Enforce a configurable recursion depth limit and reject patterns exceeding the threshold.
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-102276 is a reminder that even simple utilities can harbor availability risks. Stack exhaustion through unbounded recursion is a preventable class of denial-of-service flaw—one that appears obvious in hindsight but is easy to overlook when building a parsing or expansion library. By upgrading brace-expansion to 5.0.12, 3.0.7, 2.1.5, or 1.1.19, and by implementing your own input-depth guards in services that accept patterns, you eliminate both the immediate risk and the architectural vulnerability that created it.