Sep 30, 2026·7 min read·5 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.
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.
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.
A security-critical desynchronization vulnerability exists in fast-uri versions 4.1.3 and 4.1.4. Due to incorrect order-of-operations, the mailto scheme parser validates raw percent-encoded parameter keys instead of normalized keys, but subsequently decodes and writes them into a generic headers object. When the parsed URI is serialized, these keys are re-emitted literally, allowing attackers to bypass validation boundaries and smuggle unauthorized recipients, subjects, or body parameters in downstream mailing applications.