Sep 30, 2026·7 min read·4 visits
Uncontrolled recursion in nested brace groups allows unauthenticated attackers to crash the Node.js process via a stack overflow using compact (~6KB) inputs.
A stack-based Denial of Service (DoS) vulnerability via uncontrolled recursion in the brace-expansion library prior to versions 1.1.20, 2.1.6, 3.0.8, and 5.0.11 allows unauthenticated remote attackers to trigger native stack exhaustion, terminating the Node.js process via a crafted payload containing deeply nested brace groups.
The brace-expansion npm package is a widely used utility designed to resolve shell-like brace patterns into expanded string arrays. It is commonly integrated into toolchains, file-matching utilities, and security-critical path parsing systems such as the minimatch and glob libraries. Because these utilities often ingest user-provided input to locate resources or map system actions, the expansion parser forms an important security boundary for incoming input strings.
This vulnerability belongs to the class of Uncontrolled Recursion (CWE-674) leading to Uncontrolled Resource Consumption (CWE-400). When the library is supplied with deeply nested brace groups, the internal parsing function executes recursively for each level of nesting. Without structural limits on recursion depth, this execution pattern exhausts the allocated call stack of the underlying runtime engine.
The resulting impact is a severe, process-terminating Denial of Service (DoS). Because Node.js operates on a single-threaded event loop, an unhandled stack overflow exception terminates the application process abruptly. In environments lacking automated process restoration, this completely disables the application's availability to all users.
To resolve shell-like brace patterns (such as a{b,c}d into abd and acd), the brace-expansion library evaluates individual structural components recursively. A prior vulnerability, CVE-2026-14257, was mitigated by converting tail-parsing recursion into an iterative loop. This adjustment protected the library against chained sequences of expansion groups (such as a{b,c}d{e,f}...), ensuring that sequentially parsed blocks did not consume call stack frames.
However, the previous mitigation left nested brace groups completely unconstrained. The internal expansion engine relies on the function expand_ to process subdivisions of nesting. Uncontrolled recursion occurs in two distinct parsing conditions:
Comma-Member Sets: In structures containing alternative execution paths within comma-separated segments (such as {a,{b,c}}), the parser recursively expands the internal structures before mapping the parent sequence.
Single-Part Set Wrapping: In structures where a set consists of a single part requiring subsequent re-wrapping (such as x{{a,b}}y mapping to x{a}y and x{b}y), the code initiates a recursion pass on the inner content.
The V8 JavaScript engine enforces a hard limit on the native call stack size to prevent runaway memory consumption. If an untrusted string containing several thousand nested brackets is passed to the parser, the continuous allocation of activation records exceeds this stack frame ceiling. The environment throws a RangeError: Maximum call stack size exceeded exception. Because the exception is generated within the native engine loop of the library, applications rarely wrap these utilities in exception handling blocks, leading to immediate process termination.
To understand the precise vulnerability and its mitigation, we analyze the changes introduced across the package branches. The patch implements a recursion-tracking mechanism by passing and incrementing a depth variable through the call tree.
The vulnerable code path did not enforce any structural depth tracking. In the TypeScript implementation, the entry-point function invoked the parser with hardcoded execution parameters:
// Vulnerable invocation path
return expand_(escapeBraces(str), max, maxLength, true).map(
unescapeBraces,
)The modified code introduces a default safety constant EXPANSION_MAX_DEPTH = 1000 and configures the main execution path to pass a starting depth of 0 while exposing maxDepth to users:
// Patched entry-point configuration
export const EXPANSION_MAX_DEPTH = 1_000
export function expand(str: string, options: BraceExpansionOptions = {}) {
// ...
const {
max = EXPANSION_MAX,
maxLength = EXPANSION_MAX_LENGTH,
maxDepth = EXPANSION_MAX_DEPTH,
} = options
return expand_(escapeBraces(str), max, maxLength, maxDepth, 0, true).map(
unescapeBraces,
)
}Inside the recursive expand_ function, a strict guard is positioned at the very top of the function body. If the tracking parameter depth exceeds the configured maxDepth, the recursion terminates early and returns the string literally:
function expand_(
str: string,
max: number,
maxLength: number,
maxDepth: number,
depth: number,
isTop: boolean,
): string[] {
// Guard clause against deep nesting exhaustion
if (depth > maxDepth) {
return [str]
}
// ...The recursive calls for both comma-member processing and single-part re-wrapping are modified to increment the depth parameter:
// Single-part set wrapping recursion path
n = expand_(n[0], max, maxLength, maxDepth, depth + 1, false).map(
embrace,
)
// ...
// Comma-member expansion recursion path
const expanded = expand_(
n[j] as string,
max,
maxLength,
maxDepth,
depth + 1,
false,
)By transitioning the return type to a literal string array once the maximum depth is violated, the function avoids raising an exception entirely. This ensures that the function remains total (non-throwing) and executes deterministically regardless of input complexity.
Exploiting this vulnerability does not require authentication or specific configuration parameters. The attacker must only identify an application endpoint that accepts a string and passes it into a component utilizing brace-expansion. Because the library is a common dependency of glob-matching libraries, applications that accept search queries or route definitions are vulnerable.
Because the execution limiters introduced in prior patches only restricted total string length (EXPANSION_MAX_LENGTH) and total output item count (EXPANSION_MAX), they are ineffective at preventing this crash. A payload containing nested braces produces a final output of negligible size and length, bypassing both checks. For instance, {{{...a,b...}}} results in only a single expansion array with two strings, but the depth of the tree is what triggers the crash.
Two specific payload formats can be leveraged to trigger process termination depending on the targeted code-level handling:
// Payload A: Single-Part Nested Set Shape (~6KB)
const expand = require('brace-expansion');
const payload = '{'.repeat(10000) + 'a,b' + '}'.repeat(10000);
expand(payload); // Instantly triggers RangeError and crashes Node.js// Payload B: Comma-Member Nested Set Shape (~15.6KB)
const expand = require('brace-expansion');
const payload = '{a,'.repeat(10000) + 'z' + '}'.repeat(10000);
expand(payload); // Crashes the process via native call stack exhaustionThese payloads are highly compact, meaning standard HTTP request body limiters (which typically allow up to 100KB or 1MB) will fail to block them. This disparity allows attackers to exhaust the host's capabilities with minimal network overhead.
The primary security impact of CVE-2026-102278 is a complete and low-cost Denial of Service (DoS) of the targeted Node.js process. In single-threaded JavaScript environments, an unhandled exception of this nature stops the entire event loop, dropping all active connections and preventing the server from handling subsequent requests.
The CVSS v3.1 base score for this vulnerability is assessed at 7.5 (High), with the vector string CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H. The severity is elevated because the attack can be launched remotely without credentials, is simple to execute, requires no user interaction, and results in a total loss of availability for the component.
In containerized or cloud-native environments, container runtimes may automatically restart the crashed process. However, if an attacker repeatedly sends the payload, the rapid loop of crashes and restarts—often called a crash-loop state—will quickly exhaust container resource limits, raise orchestration alerts, and degrade the performance of adjacent services sharing the same node.
The definitive resolution for this vulnerability is upgrading the brace-expansion library to a patched release. Maintainers have released fixes across all major version branches. Organizations should audit their dependency trees and enforce the following upgrade paths:
1.x branches, upgrade to 1.1.20 or higher.2.x branches, upgrade to 2.1.6 or higher.3.x branches, upgrade to 3.0.8 or higher.4.x or 5.x branches, upgrade to 5.0.11 or higher.If upgrading is not immediately possible, applications should implement input-validation middleware to inspect string inputs for repeated brace patterns. Rejecting inputs that exceed a specific number of nested braces (for example, preventing sequences of more than 10 consecutive { or } characters) effectively mitigates the exploit path. Additionally, configuring a process manager such as pm2 or using clustered Node.js configurations can maintain service availability by automatically spawning worker processes when an unhandled crash occurs.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
brace-expansion juliangruber | < 1.1.20 | 1.1.20 |
brace-expansion juliangruber | >= 2.0.0, < 2.1.6 | 2.1.6 |
brace-expansion juliangruber | >= 3.0.0, < 3.0.8 | 3.0.8 |
brace-expansion juliangruber | >= 4.0.0, < 5.0.11 | 5.0.11 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-674 |
| Attack Vector | Network (Unauthenticated) |
| CVSS | 7.5 (High) |
| EPSS | 0.0035 (26th percentile) |
| Impact | Denial of Service (Process Termination) |
| Exploit Status | Proof-of-Concept |
| KEV Status | Not Listed |
The software directs the code to recurse, but it does not limit or control the number of recursive loops, leading to stack exhaustion.
CVE-2026-102276 is a high-severity Denial of Service (DoS) vulnerability impacting the 'brace-expansion' library, a popular Node.js utility designed to expand brace patterns into combinatorial lists. Due to uncontrolled recursion and argument-list stack exhaustion within the internal parseCommaParts function, remote attackers can trigger an unhandled RangeError that abruptly terminates the Node.js process.
An uncontrolled resource consumption vulnerability exists in the brace-expansion JavaScript library. Due to an algorithmic flaw in parsing a legacy Bash-compatibility quirk involving {a},b}-shaped expansion structures, untrusted inputs containing many trailing closing braces trigger successive full-input rescans. This behavior yields quadratic CPU time complexity and high memory overhead, allowing remote, unauthenticated attackers to cause a Denial of Service (DoS) by blocking the single-threaded Node.js event loop.
Moment.js versions 2.29.2 through 2.30.1 are vulnerable to a Path Traversal flaw (CWE-27) on server-side Node.js environments when dynamic locales are configured. The vulnerability stems from an object-coercion bypass in the locale-name sanitization routine, which assumes incoming variables are string primitives. An attacker can pass a structured object with custom 'match' and 'toString' properties to bypass regex-based directory checks, leading to arbitrary file loading via Node's internal 'require()' call.
A denial-of-service vulnerability exists in the ip-address npm package prior to version 10.7.1. The library fails to limit the length of input strings parsed by the Address4 and Address6 constructors. When parsing highly malformed addresses, the diagnostic parser runs a synchronous regular expression search-and-replace that generates descriptive HTML error messages. Passing an excessively long string containing invalid characters causes severe memory amplification and CPU starvation, resulting in a thread hang or process crash in Node.js applications.
The ip-address library is vulnerable to a logical bypass in its subnet containment methods. The functions isInSubnet() and isHostInSubnet() do not verify that compared addresses belong to the same IP family before performing masking checks. Under specific circumstances, an IPv6 address can share leading bit patterns with an IPv4 subnet, causing the containment check to evaluate as true. This allows attackers to bypass access control lists, firewalls, and server-side request forgery protection layers in applications relying on the library.
CVE-2026-101894 is a critical path traversal vulnerability in @xhmikosr/decompress before versions 10.2.2 and 11.1.4, stemming from an incomplete hardening bypass of CVE-2026-53486 where static lexical containment checks fail to detect kernel-level resolution of crafted symlink chains, allowing arbitrary local file modification and execution.