Sep 30, 2026·7 min read·51 visits
The brace-expansion JavaScript library suffers from an O(N^2) quadratic complexity vulnerability when parsing legacy {a},b}-shaped structures with many trailing braces. This allows remote attackers to trigger a Denial of Service by blocking the single-threaded Node.js event loop.
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.
The brace-expansion JavaScript library is a widely utilized package designed to perform shell-like brace expansion, mirroring the behavior found in shells such as sh and bash. It is a foundational utility used extensively in pattern matching and file expansion within Node.js ecosystems, often serving as a dependency for libraries like minimatch and glob.
The vulnerability is classified under CWE-400 (Uncontrolled Resource Consumption) and CWE-407 (Inefficient Algorithmic Complexity). It manifests when the parser attempts to resolve a legacy compatibility quirk. When processing a specific structure containing a sequence of trailing closing braces and a trailing comma set, the parsing state machine falls back to an inefficient iterative rewriting scheme.
An attacker capable of submitting arbitrary strings to any application interface that processes inputs via brace-expansion can exploit this flaw. Because Node.js is single-threaded, blocking the event loop with this computationally intensive processing loop prevents the runtime from handling concurrent network connections, leading to application-wide denial of service.
Bash retains a legacy behavior where a brace group followed by a comma set expansions despite not forming a standard option or sequence set. For example, the expression {a},b} expands into a} and b. To replicate this quirk, the brace-expansion parser identifies potential {a},b}-shaped patterns, performs an in-place string modification, and restarts the scanner loop from the beginning of the string.
When an attacker provides a payload consisting of a single valid brace group, followed by a large number of trailing closing braces, and a terminating comma set (such as '{a}' + '}'.repeat(N) + ',z}'), the parser becomes trapped in a recursive-like loop. On the first pass, the parser detects the group {a}. Because the substring after this group contains a comma and a closing brace, it matches the legacy pattern, replaces the first closing brace with an escaped sentinel string (escClose), and restarts the scan.
On the subsequent pass, the parser scans from the beginning. It bypasses the previously escaped brace and treats the next literal trailing closing brace as the group terminator. This second brace is rewritten, and the scan restarts again. This process repeats for every single trailing closing brace in the input string.
This behavior has severe performance implications. For an input with N trailing braces, the parser performs N complete rescans of the entire string, resulting in quadratic time complexity ($O(N^2)$). Furthermore, because each rewrite replaces a single-byte closing brace with a multi-byte escClose sentinel string (approximately 25 characters), the size of the processing string expands linearly, creating significant heap allocation overhead and garbage collection pressure.
The vulnerability lies within the core parsing routine, specifically inside the nested matching loop. Before the patch, the parser evaluated the legacy Bash pattern and triggered an unrestricted continuation loop without tracking iteration depth or frequency.
// Vulnerable code pattern
const isOptions = m.body.indexOf(',') >= 0
if (!isSequence && !isOptions) {
// Pattern identified as {a},b}
if (m.post.match(/,(?!,).*\}/)) {
str = m.pre + '{' + m.body + escClose + m.post
isTop = true
continue // Scan is restarted from the beginning without a limit
}
}The official remediation introduces EXPANSION_MAX_REWRITES to bound the scan restarts. Below is the annotated patched code structure demonstrating the restriction placed on loop iterations:
// Patched code pattern
export const EXPANSION_MAX_REWRITES = 1000
function expand (str, max, maxLength, maxDepth, depth, maxRewrites, isTop) {
let rewrites = 0
// ... inner parser loop ...
const isOptions = m.body.indexOf(',') >= 0
if (!isSequence && !isOptions) {
// Check the rewrite limit before attempting legacy modification
if (rewrites < maxRewrites && m.post.match(/,(?!,).*\}/)) {
rewrites++
str = m.pre + '{' + m.body + escClose + m.post
isTop = true
continue // Restarts scan, but capped at 1000 iterations
}
}By capping the iteration count, the parser prevents unbounded execution on malicious inputs. Once the threshold of 1000 rewrites is exceeded, the parser ceases rewriting, exits the loop, and processes the remaining segment as a literal string.
To exploit this vulnerability, an attacker must submit a structured payload containing a leading simple brace group, a high count of consecutive closing braces, and a final comma set. Because the payload relies solely on common characters, it easily bypasses standard sanitization mechanisms that only look for malicious command sequences or script tags.
const expand = require('brace-expansion');
// Construct a payload containing 80,000 trailing braces
const n = 80000;
const payload = '{a}' + '}'.repeat(n) + ',z}';
console.log(`[+] Running expansion with ${n} trailing braces...`);
const startTime = performance.now();
// Trigger the vulnerable parsing loop
const result = expand(payload);
const endTime = performance.now();
console.log(`[+] Completed in ${(endTime - startTime).toFixed(2)} ms`);When executed on an unpatched environment, the single-threaded Node.js event loop blocks entirely. During this execution window, the application is unable to process incoming requests, complete asynchronous database queries, or respond to health check probes. This leads to connection timeouts and service unresponsiveness.
The mitigation successfully blocks the infinite rewriting path by enforcing EXPANSION_MAX_REWRITES = 1000. However, security engineers should consider the architectural implications of how the state is tracked.
The rewrites variable is scoped locally within each execution context of the recursive expand function. When processing nested expansion sequences, each nested call receives its own independent allocation of 1000 rewrites. An attacker can theoretically design a multi-layered nested structure to bypass the 1000-rewrite ceiling globally, though the total impact is bounded by the separate EXPANSION_MAX_DEPTH (1000) and EXPANSION_MAX_LENGTH (4,000,000) limits.
Another residual behavior is that when the rewrite limit is reached, the library silently stops expanding and treats the remaining portion literally. If downstream components expect fully expanded arrays for access control or command parsing, this behavior can introduce parser differential issues where the application logic processes the literal string representation differently than intended.
The primary remediation step is to upgrade the brace-expansion dependency to a safe release version. The fix has been backported across all active branches to maintain backward compatibility.
# Verify dependency status and upgrade paths
npm audit
# Upgrade brace-expansion to the latest patched version
npm install brace-expansion@latestIf immediate dependency updates are not possible, deploy a Web Application Firewall (WAF) rule to block incoming request parameters containing repetitive closing braces. The following pattern matches payloads trying to exploit this behavior:
\{[^}]+\}[}]{10,}.*,This regex detects the necessary sequence of a valid brace group, followed by ten or more consecutive closing braces, followed by a trailing comma, allowing security teams to drop matching requests at the edge.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L| Product | Affected Versions | Fixed Version |
|---|---|---|
brace-expansion Julian Gruber | < 1.1.21 | 1.1.21 |
brace-expansion Julian Gruber | >= 2.0.0, < 2.1.7 | 2.1.7 |
brace-expansion Julian Gruber | >= 3.0.0, < 3.0.9 | 3.0.9 |
brace-expansion Julian Gruber | >= 4.0.0, < 5.0.12 | 5.0.12 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-400, CWE-407 |
| Attack Vector | Network (Remote) |
| CVSS v3.1 Score | 5.3 |
| EPSS Score | 0.00301 |
| Impact | Availability (Denial of Service) |
| Exploit Status | Proof-of-Concept |
| KEV Status | Not Listed |
The software does not properly control the allocation and maintenance of a limited resource, thereby enabling an actor to influence the amount of resources consumed, leading to 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.