high · 7.5CVE-2026-14257Jul 24, 2026

CVE-2026-14257: brace-expansion Unbounded Expansion Length DoS (OOM Crash)

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

Passing a string of 1,500 chained brace groups to brace-expansion's expand() causes an uncatchable out-of-memory crash in Node.js, taking down any server that processes user-supplied glob patterns.

Packagebrace-expansion
Ecosystemnpm
Affected<= 5.0.7
Fixed in5.0.8
CVE-2026-14257: brace-expansion Unbounded Expansion Length DoS (OOM Crash)

The problem

brace-expansion's expand() has long capped the *number* of results via the max option (default 100,000), but nothing capped the *length* of each result. Chaining N brace groups such as '{a,b}'.repeat(N) keeps result count at max while making every result N characters long, so total allocated memory scales as max * N.

Because V8 materializes intermediate cons-string arrays at every recursion level and they stay reachable throughout the call chain, memory grows without bound. At N=1,500 (a ~7.5 KB input) a default Node process hits a fatal V8 OOM. The error is uncatchable: try/catch around expand() does not help, and the entire process dies.

Proof of concept

A working proof-of-concept for CVE-2026-14257 in brace-expansion, with the exact payload below.

javascript
const { expand } = require('brace-expansion')

// ~7.5 KB input. Crashes the process with a fatal, uncatchable OOM:
//   FATAL ERROR: ... JavaScript heap out of memory
// try/catch never fires — the process is already dead.
try {
  expand('{a,b}'.repeat(1500))
} catch (e) {
  // never reached
}

The loop guard in expand_() is expansions.length < max, which stops producing new strings once the count hits max. It does nothing about string length. Each recursion level builds up to max strings that are one character longer than the level below, and because the post array from the deeper level is still referenced by each cons-string, all intermediate arrays stay live simultaneously.

Memory therefore grows as O(max * N) with no upper bound enforced on N.

The patch (commit a1bd33999ea) introduces EXPANSION_MAX_LENGTH (default 4,000,000 characters) and a new maxLength option. The fix tracks cumulative output length inside the same output-building loops that already enforce max, and truncates once the character budget is spent.

Intermediate arrays at every recursion level are bounded by the same limit, so the cons-string chain is cut off before it can exhaust the heap. CWE-770 (Allocation of Resources Without Limits or Throttling).

The fix

Upgrade brace-expansion to 5.0.8 (or the backported patches for v1, v2, v3). The release adds a maxLength option (default 4,000,000 total characters) that truncates output once the character budget is spent, mirroring how max already truncates result count. If you cannot upgrade immediately, pass a small explicit max and maxLength, or avoid forwarding untrusted strings to expand() or any API that uses minimatch/glob brace patterns internally.

Reporter not attributed.

References: [1][2][3][4][5][6]

Related research