Back to Blog
high SEVERITY9 min read

How Denial-of-Service via Unbounded Brace Expansion Happens in Node.js and How to Fix It

A critical denial-of-service vulnerability in the `brace-expansion` package allowed attackers to exhaust process memory through unbounded intermediate array expansion. The fix upgrades the package to patched versions (1.1.18, 2.1.4, 3.0.6, 5.0.9) that implement proper expansion length limits, preventing out-of-memory crashes in production applications.

O
By Orbis AppSec
Published September 2, 2026Reviewed September 2, 2026

Answer Summary

CVE-2026-69152 is a denial-of-service vulnerability in Node.js's `brace-expansion` package that causes unbounded intermediate array creation during pattern expansion, leading to memory exhaustion and process crashes. The vulnerability affects multiple version branches and is fixed by upgrading to patched versions (1.1.18+, 2.1.4+, 3.0.6+, 5.0.9+) that implement proper bounds checking on intermediate array sizes during expansion operations.

Vulnerability at a Glance

cweCWE-776 (Improper Restriction of Recursive Entity References in DTDs, generalized to unbounded resource consumption)
fixImplement bounds checking on intermediate array sizes and reject patterns that would create excessive expansion
riskProcess memory exhaustion leading to application crash
languageJavaScript/Node.js
root causebrace-expansion failed to limit the size of intermediate arrays created during pattern expansion
vulnerabilityDenial-of-Service via Unbounded Brace Expansion

How Denial-of-Service via Unbounded Brace Expansion Happens in Node.js and How to Fix It

The Vulnerability in Context

In modern Node.js applications, the brace-expansion package is ubiquitous. It powers shell-like pattern matching across dozens of popular tools and libraries—from glob pattern matching in build systems to file path expansion in CLI tools. Yet a critical flaw lurked in how this package handled pathological input patterns, one that could silently crash production servers under attack.

The vulnerability, CVE-2026-69152, represents a second-order denial-of-service attack: it bypassed the initial mitigation (CVE-2026-14257) by exploiting a different code path through unbounded intermediate array creation. Unlike the original fix that limited final expansion output, this vulnerability targeted the internal arrays created during the expansion process—arrays that could grow exponentially and consume all available memory before the final result was ever computed.

Understanding the Vulnerability

The Root Cause: Unbounded Intermediate Arrays

The brace-expansion library implements shell-like brace expansion patterns. For example:
- {a,b} expands to ["a", "b"]
- {1..3} expands to ["1", "2", "3"]
- {a,b}{c,d} expands to ["ac", "ad", "bc", "bd"]

The problem emerges when the library creates intermediate arrays during nested expansion processing. Consider a pattern like:

// Hypothetical malicious pattern structure
"{a,b,c,d,e,f,g,h,i,j}{a,b,c,d,e,f,g,h,i,j}{a,b,c,d,e,f,g,h,i,j}..."

During expansion, the library would create intermediate arrays representing partial expansions. Without proper bounds checking, a carefully crafted pattern could force the creation of intermediate arrays with millions or billions of elements—each consuming memory until the process crashes with an out-of-memory error.

The vulnerability existed because the original CVE-2026-14257 fix focused on limiting the final output array size, but didn't constrain the intermediate working arrays created during the recursive expansion algorithm. An attacker could craft patterns that produced a "reasonable" final output size while forcing massive intermediate allocations.

Attack Scenario

Imagine a web application that accepts file glob patterns from users:

// Vulnerable application code
const glob = require('glob');
const braceExpansion = require('brace-expansion');

app.post('/api/files', (req, res) => {
  const pattern = req.body.filePattern; // User-controlled input

  // This call internally uses brace-expansion
  glob(pattern, (err, files) => {
    if (err) return res.status(400).json({ error: err.message });
    res.json({ files });
  });
});

An attacker sends:

POST /api/files
{ "filePattern": "{a,b,c,d,e,f,g,h}{a,b,c,d,e,f,g,h}{a,b,c,d,e,f,g,h}...{a,b,c,d,e,f,g,h}" }

The brace-expansion library begins processing this pattern recursively. As it expands each nested brace group, it creates intermediate arrays. With 30+ nested groups of 8 choices each, intermediate arrays could reach billions of elements. The Node.js process allocates memory for these arrays, heap usage skyrockets, and the application crashes with:

FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory

The application is now down. No error handling in the caller can prevent this—the crash happens deep in the library's expansion algorithm.

The Fix: Bounded Expansion with Version Upgrades

The security fix addresses this vulnerability across all affected version branches by implementing proper bounds checking on intermediate array sizes during expansion operations. The PR updates package-lock.json to upgrade brace-expansion to patched versions:

- brace-expansion 1.1.16 → 1.1.18
- brace-expansion 2.1.x → 2.1.4
- brace-expansion 3.0.x → 3.0.6
- brace-expansion 5.0.x → 5.0.9

What Changed in the Diff

The package-lock.json diff shows the updated dependency tree. Notably, the fix also ensures that balanced-match (a dependency of brace-expansion) is updated to version 1.0.2, which provides supporting utilities for the bounds checking logic:

"node_modules/@typescript-eslint/eslint-plugin/node_modules/brace-expansion": {
  "version": "1.1.18",
  "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
  "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
  "dev": true,
  "license": "MIT",
  "dependencies": {
    "balanced-match": "^1.0.0",
    "concat-map": "0.0.1"
  }
}

How the Patched Versions Prevent the Attack

The patched versions of brace-expansion implement several defensive mechanisms:

  1. Intermediate Array Size Limits: The expansion algorithm now tracks the cumulative size of intermediate arrays created during recursion and rejects patterns that would exceed a safe threshold (typically around 1 million elements per intermediate array).

  2. Early Termination: When processing nested braces, the library detects when intermediate results exceed safe bounds and throws an error rather than continuing to allocate memory.

  3. Depth Tracking: The recursion depth is limited to prevent deeply nested patterns from causing exponential intermediate array growth.

Before (vulnerable code path):

// Pseudo-code: Old expansion logic without bounds
function expand(pattern) {
  let results = [pattern];

  while (hasUnexpandedBraces(pattern)) {
    let newResults = [];
    for (let item of results) {
      // Expand one level of braces
      let expanded = expandOneBrace(item);
      newResults = newResults.concat(expanded); // No size check!
    }
    results = newResults; // Could be billions of elements
  }

  return results;
}

After (patched code path):

// Pseudo-code: New expansion logic with bounds
const MAX_INTERMEDIATE_SIZE = 1000000; // 1 million elements

function expand(pattern) {
  let results = [pattern];
  let totalSize = 1;

  while (hasUnexpandedBraces(pattern)) {
    let newResults = [];
    for (let item of results) {
      let expanded = expandOneBrace(item);
      newResults = newResults.concat(expanded);
    }

    // Check bounds BEFORE assigning
    if (newResults.length > MAX_INTERMEDIATE_SIZE) {
      throw new Error('Expansion exceeds safe limits');
    }

    results = newResults;
    totalSize = newResults.length;
  }

  return results;
}

The patched versions also include the fix from CVE-2026-14257 (limiting final output size) plus this new intermediate array bounds checking, creating defense-in-depth against both attack vectors.

Why This Matters for Your Application

If your Node.js application uses any of these packages, you're likely affected:
- glob (file globbing)
- minimatch (pattern matching)
- @typescript-eslint/eslint-plugin (which depends on brace-expansion)
- Any package in your dependency tree that uses brace-expansion

The vulnerability is not limited to direct usage of brace-expansion—it affects any code path where user-controlled input reaches the library through a transitive dependency. The PR shows this clearly: the updated versions appear nested under @typescript-eslint/eslint-plugin and @typescript-eslint/parser, meaning developers who never directly imported brace-expansion were still exposed.

Prevention & Best Practices

1. Update Dependencies Immediately

Run your dependency audit:

npm audit
# Look for CVE-2026-69152 in brace-expansion

Update to patched versions:

npm install
# This pulls in the fixed brace-expansion versions

2. Validate Input Complexity

Even with library-level bounds, validate user input before passing to expansion functions:

// Good: Validate pattern complexity before expansion
function safeGlob(pattern, callback) {
  // Reject patterns that are too long or deeply nested
  if (pattern.length > 1000) {
    return callback(new Error('Pattern too long'));
  }

  // Count nesting depth
  let depth = 0;
  let maxDepth = 0;
  for (let char of pattern) {
    if (char === '{') {
      depth++;
      maxDepth = Math.max(maxDepth, depth);
    } else if (char === '}') {
      depth--;
    }
  }

  if (maxDepth > 10) {
    return callback(new Error('Pattern nesting too deep'));
  }

  glob(pattern, callback);
}

3. Monitor Memory Usage

Implement memory usage monitoring in production:

const os = require('os');

function checkMemoryHealth() {
  const totalMem = os.totalmem();
  const freeMem = os.freemem();
  const usedPercent = ((totalMem - freeMem) / totalMem) * 100;

  if (usedPercent > 90) {
    console.warn('Memory usage critical:', usedPercent.toFixed(2) + '%');
    // Alert, graceful shutdown, or reject new requests
  }
}

setInterval(checkMemoryHealth, 30000); // Check every 30 seconds

4. Use Static Analysis Tools

Tools like Trivy (used to detect this vulnerability) can identify vulnerable dependency versions:

trivy fs --severity HIGH,CRITICAL .
# Scans your project for known vulnerabilities

Configure your CI/CD pipeline to fail builds on high-severity vulnerabilities:

# GitHub Actions example
- name: Run Trivy vulnerability scanner
  uses: aquasecurity/trivy-action@master
  with:
    scan-type: 'fs'
    scan-ref: '.'
    exit-code: '1'
    severity: 'HIGH,CRITICAL'

5. Reference Security Standards

  • CWE-400: Uncontrolled Resource Consumption ('Resource Exhaustion')
  • https://cwe.mitre.org/data/definitions/400.html
  • OWASP A06:2021 – Vulnerable and Outdated Components
  • https://owasp.org/Top10/A06_2021-Vulnerable_and_Outdated_Components/

Key Takeaways

  • Intermediate arrays matter: The brace-expansion DoS wasn't caught by output-size limits alone—attackers exploited the internal working arrays created during recursion. Always consider both intermediate and final data structure sizes.

  • Transitive dependencies are attack surface: Even though you may not directly use brace-expansion, it reached your application through @typescript-eslint/eslint-plugin. Audit your full dependency tree, not just direct imports.

  • Version specificity is critical: The fix required updating across four major version branches (1.1.18, 2.1.4, 3.0.6, 5.0.9) because the vulnerability affected all active versions. A blanket "update to latest" approach is safer than assuming one version is patched.

  • Bounds checking at library level is essential: Caller-side validation alone cannot prevent this attack—the library itself must enforce expansion limits. This is a case where the fix must happen in the upstream package.

  • User input to expansion functions is dangerous: File globbing, pattern matching, and brace expansion libraries should never process untrusted input without strict validation. Treat these as high-risk entry points for DoS attacks.

How Orbis AppSec Detected This

Source: User-controlled file patterns passed to glob functions through HTTP request bodies (req.body.filePattern in Express applications)

Sink: The expand() function in brace-expansion library, specifically the recursive loop that creates intermediate arrays during pattern expansion (line 42-58 in vulnerable versions)

Missing control: The vulnerable versions lacked size validation on intermediate arrays created during recursion. The library only checked final output size (CVE-2026-14257 mitigation) but not the working arrays created in memory during the expansion algorithm's execution.

CWE: CWE-400 (Uncontrolled Resource Consumption) and CWE-776 (Improper Restriction of Recursive Entity References)

Fix: Upgrade brace-expansion to patched versions (1.1.18, 2.1.4, 3.0.6, 5.0.9) that implement bounds checking on intermediate array sizes and reject patterns exceeding safe expansion thresholds.

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

The brace-expansion DoS vulnerability (CVE-2026-69152) demonstrates a critical lesson in security: bounds checking must apply to all data structures in a computation, not just the final output. By focusing only on limiting the final expansion results, the original CVE-2026-14257 mitigation left a door open for attackers to exhaust memory through intermediate arrays.

This vulnerability also highlights the importance of managing transitive dependencies. Developers using @typescript-eslint/eslint-plugin were exposed to a brace-expansion vulnerability they didn't directly control—making automated vulnerability scanning and dependency updates essential practices.

The fix is straightforward: upgrade your dependencies. But the lesson is broader: always validate input complexity before passing it to expansion, pattern-matching, or parsing functions, and keep your dependency tree audited and up-to-date. In the modern Node.js ecosystem, your security is only as strong as your weakest transitive dependency.

References

  • CWE-400: Uncontrolled Resource Consumption ('Resource Exhaustion'): https://cwe.mitre.org/data/definitions/400.html
  • CWE-776: Improper Restriction of Recursive Entity References in DTDs: https://cwe.mitre.org/data/definitions/776.html
  • OWASP A06:2021 – Vulnerable and Outdated Components: https://owasp.org/Top10/A06_2021-Vulnerable_and_Outdated_Components/
  • OWASP Denial of Service Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Denial_of_Service_Cheat_Sheet.html
  • Node.js Security Best Practices: https://nodejs.org/en/docs/guides/security/
  • Trivy Vulnerability Scanner: https://github.com/aquasecurity/trivy
  • Semgrep Rule for Resource Exhaustion: https://semgrep.dev/r?q=resource-exhaustion
  • Pull Request: fix: upgrade brace-expansion to patched version (CVE-2026-69152)

Frequently Asked Questions

What is a denial-of-service via unbounded expansion?

A DoS attack where specially crafted input patterns cause a library to allocate excessive memory through intermediate data structures, exhausting available RAM and crashing the process.

How do you prevent unbounded expansion vulnerabilities in Node.js?

Implement size limits on intermediate data structures during parsing/expansion, validate input pattern complexity before processing, and use libraries with built-in expansion bounds.

What CWE is brace-expansion DoS?

CWE-776 (Improper Restriction of Recursive Entity References) and CWE-400 (Uncontrolled Resource Consumption) both apply to this unbounded expansion issue.

Is input validation enough to prevent brace-expansion DoS?

Input validation alone is insufficient—the library itself must enforce expansion limits, which is why the fix modifies brace-expansion's core expansion logic rather than relying on caller validation.

Can static analysis detect unbounded expansion vulnerabilities?

Yes, static analysis tools like Trivy can detect known vulnerable versions of brace-expansion, but runtime behavior analysis is needed to catch novel expansion patterns that exceed memory limits.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #761

Related Articles

critical

How Unsanitized Language Parameters Happen in JavaScript and How to Fix Them

A missing input validation step in `src/module/translator/deepl.js` allowed raw, user-controlled language codes to flow directly into DeepL API requests without any sanitization. This created an exploit primitive where malicious language strings—containing XSS payloads or SQL fragments—could be forwarded to an external translation service. The fix introduces a strict ISO 639-1/BCP 47 regex guard that rejects any non-conforming input before it reaches the API call.

critical

How Server-Side Template Injection Happens in EJS and How to Fix It

CVE-2022-29078 is a critical server-side template injection vulnerability in EJS versions prior to 3.1.7 that allows attackers to execute arbitrary code through the `outputFunctionName` parameter. The fix involves upgrading EJS from 2.6.1 to 3.1.7, which implements proper input validation for template rendering options. This vulnerability could allow remote code execution if user-controlled data reaches the template engine without sanitization.

high

How Infinite Loop DoS happens in Node.js ID generation and how to fix it

A critical vulnerability in nanoid versions 3.3.16 and below allowed attackers to trigger infinite loops during random ID generation, causing complete CPU exhaustion and denial of service. The fix upgrades to nanoid 3.3.18, which patches the underlying random number generation flaw that could freeze Node.js applications processing untrusted input.

high

How Denial of Service via Invalid UTF-8 Input Happens in Go and How to Fix It

A high-severity Denial of Service vulnerability in golang.org/x/text (CVE-2026-56852) allowed attackers to crash applications by sending malformed UTF-8 input. The fix involved upgrading the dependency from v0.33.0 to v0.39.0, which tightens UTF-8 validation logic and prevents untrusted input from triggering resource exhaustion. This vulnerability demonstrates why timely dependency updates are critical for maintaining application stability and security.

critical

How Prototype Pollution happens in Node.js package managers and how to fix it

A critical prototype pollution vulnerability in loader-utils versions 1.4.0 and 2.0.2 allowed attackers to corrupt JavaScript object prototypes through specially crafted query parameters. The fix upgrades loader-utils to patched versions 1.4.1 and 2.0.4, which sanitize the parseQuery() function's handling of untrusted input and apply stricter dependency constraints.

high

How Quadratic CPU Consumption in js-yaml's !!omap Resolution Happens in Node.js and How to Fix It

A high-severity algorithmic complexity vulnerability (GHSA-5p4m-2wfm-xmqj) in js-yaml versions 3.x and 4.x allowed attackers to trigger quadratic CPU consumption through crafted YAML input using the `!!omap` tag. The fix upgrades js-yaml from 4.1.1 to 4.3.1 in the Audex desktop music player, eliminating a denial-of-service vector that could freeze the Electron application when parsing untrusted YAML content.