CVE-2026-13149
ADVISORY - githubSummary
Summary
brace-expansion's expand() exhibits exponential-time - O(2ⁿ) - behavior in the number of consecutive non-expanding {} groups. A short, all-ASCII input (~90 bytes/30 groups) blocks the calling thread for minutes; a slightly longer input hangs it effectively indefinitely. Because the dominant consumers run on Node's single-threaded event loop, one small input can fully stall a worker/process.
In expand_, post is computed unconditionally at the top of the function, before the early-return branches that don't use it:
const post = m.post.length ? expand_(m.post, max, false) : ['']; // always recurses
...
if (!isSequence && !isOptions) {
if (m.post.match(/,(?!,).*\}/)) {
str = m.pre + '{' + m.body + escClose + m.post;
return expand_(str, max, true); // restart — `post` discarded
}
return [str];
}
For input like a{},{},…, the first {} is non-expanding, so control reaches the {a},b} rewrite branch - but expand_ has already recursed into post over the entire remaining tail, only to throw the result away.
Each level therefore spawns two recursive expansions over essentially the same remaining work: T(n) = 2·T(n−1) ⇒ O(2ⁿ).
The max option does not mitigate this: max only bounds the output-building loops; neither the post recursion nor the rewrite recursion consults it.
Measured on 5.0.6:
| groups (n) | input bytes | time |
|---|---|---|
| 20 | 60 | 130 ms |
| 24 | 72 | 1.9 s |
| 26 | 78 | 7.8 s |
| 30 (PoC) | 90 | ~2 min |
Proof of concept
const { expand } = require('brace-expansion');
// 30 non-expanding groups, ~90 bytes — blocks for minutes:
expand('a{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}');
Impact
Any application that passes attacker-influenced strings to brace-expansion.expand() - directly or transitively via minimatch/glob brace patterns - can be driven into a multi-minute-to-indefinite CPU hang by a tiny request, denying service on that thread/process.
Remediation
Upgrade to a patched release. The fix:
- Defers computing post until after the early-return branches (and computes it locally in the $-suffix branch), so post is only expanded when a brace set actually expands and the value is used. This alone removes the exponential.
- Converts the {a},b} rewrite from recursion to an in-function loop, so a long run of rewrites cannot grow the call stack.
Verified: the PoC drops from ~2 min to 0.55 ms, 5,000 groups complete in ~344 ms, and output is identical to 5.0.6 across a behavioral-equivalence suite (sequences, padding, $-prefix, a{},b}c, {},a}b, x{{a,b}}y, etc.). Post-fix complexity is ~O(n²) on this input class - acceptable for the security fix; a linear rewrite can be a non-urgent follow-up.
If immediate upgrade isn't possible, avoid passing untrusted input to expand() / glob brace patterns, or run such expansion under a timeout/worker.
Common Weakness Enumeration (CWE)
Inefficient Regular Expression Complexity
GitHub
CVSS SCORE
7.7highDebian
-
Ubuntu
-
CVSS SCORE
N/AmediumRed Hat
3.9
CVSS SCORE
7.5highminimos
MINI-2x57-mf9f-89v4
-
minimos
MINI-3mfh-59mq-9xc9
-
minimos
MINI-6996-wxf8-m5mx
-
minimos
MINI-7fx2-gjq3-57f9
-
minimos
MINI-7m8w-q8mq-5fvg
-
minimos
MINI-83xf-pm9r-vmgm
-
minimos
MINI-8f63-43v9-ph48
-
minimos
MINI-8qvp-9gx7-fp2p
-
minimos
MINI-9vhh-hpf2-3gfm
-
minimos
MINI-9vv8-9vpq-2wrg
-
minimos
MINI-c332-jrhp-r83f
-
minimos
MINI-cfmr-4wf2-5gxh
-
minimos
MINI-f65v-v6rq-3r2f
-
minimos
MINI-f7vr-qggw-mw6c
-
minimos
MINI-f9f2-233f-xw4x
-
minimos
MINI-g27r-c53v-2c4p
-
minimos
MINI-hf74-4qqq-5ph9
-
minimos
MINI-j4p8-748v-7qvw
-
minimos
MINI-jch7-wjr2-mpw3
-
minimos
MINI-mjrq-25h6-w7w4
-
minimos
MINI-pv2f-5wqc-9pwv
-
minimos
MINI-vw6j-cg2j-w8r9
-
minimos
MINI-w4r6-4p63-8cg8
-
minimos
MINI-w785-6prw-wph5
-
minimos
MINI-xg63-wqm5-qcw5
-