Sep 30, 2026·7 min read·56 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-128087 (GHSA-9264-jvgf-6w2f) is an incorrect behavior order (CWE-696) and improper authorization (CWE-863) vulnerability in the russh Rust SSH library, affecting versions 0.55.0 through 0.65.0. During the initial SSH key exchange (KEX) handshake, the client state machine invokes the client::Handler::kex_done() callback—providing the negotiated raw shared secret bytes—prior to validating the remote server's host key or certificate via check_server_key(). Consequently, an untrusted or rogue SSH server can complete cryptographic key agreement and force a connecting client to execute side-effecting application logic inside kex_done() before the server's identity is checked and rejected.
CVE-2026-128082 (GHSA-6vhm-j9h7-f9gh) is an uncontrolled recursion vulnerability (CWE-674) in AutoMapper, a convention-based object-object mapper for .NET. When mapping nested or self-referential types into destination models that rely on parameterized constructors, C# positional records, or recursive ConstructUsing expressions, AutoMapper evaluates child constructor arguments prior to incrementing the parent's recursion depth counter. This ordering flaw bypasses both explicit MaxDepth limits and the default depth limit of 64. An unauthenticated remote attacker can supply deeply nested input payloads to affected endpoints, exhausting the thread call stack and triggering an uncatchable System.StackOverflowException that terminates the hosting .NET process.
An information exposure vulnerability in the @opentelemetry/instrumentation-http package for OpenTelemetry JavaScript versions 0.204.0 prior to 0.222.0 causes incoming HTTP server spans to record raw, unredacted query string parameters under the url.query attribute. While outgoing client spans applied built-in and user-configured query parameter redaction, incoming server spans bypassed this sanitization completely, resulting in the transmission of sensitive credentials such as pre-signed cloud URLs and API tokens to external observability backends.
CVE-2026-128089 is an uncontrolled resource consumption vulnerability in the dnspython toolkit for Python, affecting all versions prior to 2.9.2. When parsing Well-Known Service (WKS) records from text via dns.rdtypes.IN.WKS.WKS.from_text(), the parser fails to validate that numeric service port identifiers fall within the RFC 1035 valid range of 0 through 65535. An attacker capable of supplying untrusted zone files or RDATA text strings containing an arbitrarily large service port integer can force the parser to allocate an unbounded bitmap array using an inefficient linear append loop, resulting in severe CPU exhaustion, application hangs, or Out-Of-Memory (OOM) process termination.
CVE-2026-126347 (GHSA-5cjw-f85j-cv6r) is an open redirect vulnerability (CWE-601) in Tornado, an asynchronous Python networking library and web framework. The flaw resides in tornado.web.RedirectHandler when dynamic URL redirection templates incorporate unvalidated user input. Due to a parser differential between RFC 3986 (used by Python's urllib.parse.urlsplit) and the WHATWG URL Standard (used by modern web browsers), inputs containing three or more leading slashes bypass Tornado 6.6.0's validation logic, allowing unauthenticated attackers to redirect users to arbitrary external domains.
A Denial of Service (DoS) vulnerability exists in @sveltejs/adapter-node versions 5.5.7 and 6.0.0-next.0 through 6.0.0. When a SvelteKit server route returns a direct response from a fetch() call with a Content-Type of text/event-stream, the Node.js adapter attempts to mutate the response headers to disable proxy buffering. Because headers returned directly by the WHATWG Fetch API carry an immutable guard, this mutation throws an unhandled TypeError, terminating the underlying Node.js process and denying service to all active and subsequent clients.