Back to Blog
high SEVERITY8 min read

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

CVE-2026-14257 is a high-severity Denial of Service vulnerability in the `brace-expansion` npm package (versions through 5.0.7) that allows an attacker to trigger an out-of-memory process crash by supplying a crafted string with unbounded expansion length. The fix upgrades `brace-expansion` to patched versions (5.0.8, 3.0.3, 2.1.3, or 1.1.17 depending on the major version in use), which enforce limits on expansion output size. Any Node.js project that passes user-influenced glob or path patterns

O
By Orbis AppSec
Published August 26, 2026Reviewed August 26, 2026

Answer Summary

CVE-2026-14257 is a Denial of Service (DoS) vulnerability (CWE-400: Uncontrolled Resource Consumption) in the `brace-expansion` npm package affecting all versions through 5.0.7. A maliciously crafted brace expression—such as `{a,b,c,...}` repeated thousands of times—causes the library to generate an exponentially large array of strings, exhausting heap memory and crashing the Node.js process. The fix upgrades `brace-expansion` to version 5.0.8 (or 3.0.3 / 2.1.3 / 1.1.17 for older major versions), which adds an upper bound on expansion output length, preventing runaway memory allocation.

Vulnerability at a Glance

cweCWE-400
fixUpgrade brace-expansion to 5.0.8 / 3.0.3 / 2.1.3 / 1.1.17 which cap expansion output length
riskRemote attacker can crash the Node.js process by exhausting heap memory
languageJavaScript / Node.js
root causebrace-expansion performs no limit check on the size of the generated expansion array
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 Problem Hidden in Your package-lock.json

Most developers never think twice about brace-expansion. It quietly powers glob matching in tools like minimatch, glob, mocha, and nyc—the kind of foundational utility that gets pulled into nearly every Node.js project transitively. But in all versions through 5.0.7, brace-expansion contains a high-severity Denial of Service vulnerability (CVE-2026-14257) that can crash your Node.js process with a single malformed string.

The vulnerability was flagged by Trivy in package-lock.json and fixed by upgrading to brace-expansion 5.0.8 (and corresponding patches for older major versions: 3.0.3, 2.1.3, and 1.1.17). This post explains exactly how the attack works, what changed in the fix, and how to protect your own projects.


The Vulnerability Explained

What Does brace-expansion Actually Do?

brace-expansion parses shell-style brace patterns and expands them into arrays of strings:

const expand = require('brace-expansion');

expand('{a,b,c}');         // → ['a', 'b', 'c']
expand('file{1..5}.txt');  // → ['file1.txt', 'file2.txt', ..., 'file5.txt']
expand('{a,b}{c,d}');      // → ['ac', 'ad', 'bc', 'bd']

This is useful and intentional. The problem arises when the library applies this expansion logic to adversarially crafted input without any upper bound on how large the resulting array can grow.

The Unbounded Expansion Problem

Consider what happens when you nest or repeat brace groups:

// Each level doubles the output
expand('{a,b}');                         // 2 strings
expand('{a,b}{a,b}');                    // 4 strings
expand('{a,b}{a,b}{a,b}');              // 8 strings
// ...
expand('{a,b}'.repeat(30));             // 2^30 = ~1,073,741,824 strings

A 60-character input string produces over one billion output strings. Each string is allocated on the JavaScript heap. Before the fix, brace-expansion would dutifully attempt to construct this entire array, consuming gigabytes of memory until the Node.js process was killed by the OS or threw a fatal JavaScript heap out of memory error:

FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory
 1: 0xb7c6e0 node::Abort() [node]
 2: 0xa9157e node::FatalError(char const*, char const*) [node]
 3: 0xdce59e v8::Utils::ReportOOMFailure(...) [node]

The root cause is CWE-400: Uncontrolled Resource Consumption. The expansion algorithm in versions ≤5.0.7 allocates result arrays proportional to the combinatorial product of all brace groups, with no check on the total output size.

Attack Scenario

Imagine a Node.js application that accepts a glob pattern from an HTTP query parameter to search files:

// A simplified but realistic example of a vulnerable code path
const glob = require('glob');
const app = require('express')();

app.get('/files', (req, res) => {
  const pattern = req.query.pattern; // user-controlled input
  // glob internally uses brace-expansion to parse the pattern
  glob(pattern, (err, files) => {
    res.json(files);
  });
});

An attacker sends:

GET /files?pattern={a,b}{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}

That's a 120-character query string. The server attempts to expand 2³⁰ strings, runs out of memory, and crashes—taking down the entire service. No authentication required. No special privileges. Just one HTTP request.

Even if your application doesn't directly expose glob patterns to users, any code path that passes user-influenced data through a library that depends on brace-expansion (such as minimatch, glob, or mocha's file watcher) is potentially vulnerable.


The Fix

The fix in this PR upgrades brace-expansion across all affected major version lines:

Major Version Vulnerable Patched
5.x ≤ 5.0.7 5.0.8
3.x ≤ 3.0.2 3.0.3
2.x ≤ 2.1.2 2.1.3
1.x ≤ 1.1.16 1.1.17

What Changed in the Patched Versions

The patched versions introduce an output size limit inside the expansion logic. Instead of blindly building the full combinatorial array, the library now checks whether the projected expansion size exceeds a safe threshold and throws an error (or returns an empty/truncated result) rather than attempting to allocate unbounded memory.

Conceptually, the fix adds a guard like this inside the core expansion loop:

// BEFORE (vulnerable — no size check):
function expand(str) {
  // ... parse brace groups ...
  let result = [];
  for (const combo of combinations) {
    result.push(combo); // unbounded — could be billions of entries
  }
  return result;
}

// AFTER (patched — output size is bounded):
const MAX_EXPANSION = 1_000_000; // or similar safe limit

function expand(str) {
  // ... parse brace groups ...
  const projectedSize = computeExpansionSize(groups);
  if (projectedSize > MAX_EXPANSION) {
    throw new RangeError('brace-expansion: expansion too large');
  }
  let result = [];
  for (const combo of combinations) {
    result.push(combo);
  }
  return result;
}

This means that legitimate, bounded patterns—{src,test}/**/*.js, file{1..100}.txt—continue to work exactly as before. Only pathologically large expansions are rejected.

The package-lock.json Change

The PR modifies package-lock.json to pin the resolved version of brace-expansion to the patched release. Here is the relevant portion of the diff showing the version bump pattern (the same change is applied to each occurrence of brace-expansion in the dependency tree):

- "version": "5.0.7",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz",
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz",

The PR also bumps a co-located transitive dependency, js-yaml, from 3.15.0 to 3.15.1:

- "version": "3.15.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.0.tgz",
+ "version": "3.15.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.1.tgz",
  "dev": true,
+ "license": "MIT",

This is a housekeeping update that also adds an explicit license field, consistent with the broader lockfile modernization in this PR.


Prevention & Best Practices

1. Validate User-Supplied Glob Patterns Before Expansion

Even with the patched library, defense-in-depth is valuable. If your application accepts user-supplied patterns, validate them before passing to any glob or path-expansion function:

function isSafeGlobPattern(pattern) {
  // Reject patterns with excessive brace groups
  const braceGroupCount = (pattern.match(/\{/g) || []).length;
  if (braceGroupCount > 10) return false;
  if (pattern.length > 256) return false;
  return true;
}

app.get('/files', (req, res) => {
  const pattern = req.query.pattern;
  if (!isSafeGlobPattern(pattern)) {
    return res.status(400).json({ error: 'Invalid pattern' });
  }
  glob(pattern, (err, files) => res.json(files));
});

2. Keep Dependencies Updated with Automated Scanning

Use tools like npm audit, Trivy, Snyk, or Dependabot in your CI pipeline to catch vulnerable transitive dependencies before they reach production:

# Run on every CI build
npm audit --audit-level=high

# Or with Trivy
trivy fs --scanners vuln package-lock.json

3. Use overrides in package.json for Transitive Dependency Pinning

If a transitive dependency is slow to update, npm's overrides field lets you force a specific version across the entire dependency tree:

{
  "overrides": {
    "brace-expansion": "^5.0.8"
  }
}

4. Apply Resource Limits at the Process Level

As a last line of defense, consider running Node.js with explicit heap limits and process isolation so that a single OOM event doesn't take down your entire service:

# Limit heap to 512MB; the process will crash before consuming all system memory
node --max-old-space-size=512 server.js

Pair this with a process manager like PM2 that auto-restarts crashed workers.

Security Standards Reference

  • CWE-400: Uncontrolled Resource Consumption — the root cause category for this vulnerability
  • OWASP A05:2021 – Security Misconfiguration (using known-vulnerable components)
  • OWASP Dependency-Check and npm audit are the recommended tools for detecting this class of issue

Key Takeaways

  • A 120-character input string can crash your server: The combinatorial nature of brace expansion means output size grows exponentially with input length—input size limits alone are not sufficient protection.
  • Transitive dependencies are attack surface: brace-expansion is rarely a direct dependency; it arrives via glob, minimatch, mocha, or nyc. Your package-lock.json is the ground truth for what version is actually running.
  • The fix is purely additive: Patched versions of brace-expansion only add an output-size guard. All valid, non-adversarial patterns continue to work identically—there is no behavior change for legitimate use cases.
  • Multiple major versions needed patching simultaneously: The vulnerability existed in the 1.x, 2.x, 3.x, and 5.x lines, meaning projects on any of these versions needed to upgrade to their respective patch release.
  • Static analysis caught what code review would miss: No human reviewer scanning application code would spot a vulnerable version of brace-expansion buried in a lockfile—automated scanning is essential for this class of vulnerability.

How Orbis AppSec Detected This

  • Source: User-influenced input (e.g., HTTP query parameters, file path arguments, CLI arguments) passed to any function that internally invokes brace-expansion's expand() function
  • Sink: The core expansion loop inside brace-expansion/index.js (versions ≤5.0.7), which allocates result arrays without bounding the total output size
  • Missing control: No maximum expansion size check before or during array allocation in the expand() function
  • CWE: CWE-400 — Uncontrolled Resource Consumption
  • Fix: Upgraded brace-expansion to 5.0.8 (and 3.0.3 / 2.1.3 / 1.1.17 for older major versions) in package-lock.json, which introduces an upper bound on expansion output length

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

CVE-2026-14257 is a reminder that even the most innocuous-looking utility packages can harbor high-severity vulnerabilities. brace-expansion is so ubiquitous in the Node.js ecosystem that nearly every project with a package-lock.json has it somewhere in the dependency tree. The vulnerability itself is elegant in its simplicity: a small, valid-looking input triggers exponential memory allocation, crashing the process with no authentication or special access required.

The fix is equally simple—upgrade to the patched version. But finding the vulnerability in the first place requires automated scanning of your full dependency tree, not just your direct dependencies. Make npm audit or an equivalent scanner a mandatory step in your CI pipeline, and consider tools like Orbis AppSec to automatically open fix PRs when new CVEs are published.


References

Frequently Asked Questions

What is a brace-expansion Denial of Service vulnerability?

It is a flaw where a library that expands shell-style brace patterns (e.g., `{a,b}{c,d}`) generates an unbounded number of output strings from a small crafted input, consuming all available memory and crashing the process.

How do you prevent unbounded brace expansion DoS in Node.js?

Upgrade brace-expansion to a patched version (≥5.0.8, ≥3.0.3, ≥2.1.3, or ≥1.1.17) and validate or sanitize user-supplied glob/path patterns before passing them to expansion functions.

What CWE is the brace-expansion DoS vulnerability?

CWE-400 — Uncontrolled Resource Consumption ("Resource Exhaustion"), where the program does not limit the amount of memory consumed while processing input.

Is input length validation enough to prevent brace-expansion DoS?

No. A short input string like `{a,b}{a,b}{a,b}...` repeated 30 times is only ~90 characters but produces 2³⁰ (over a billion) output strings. You must limit the *output* size, not just the input length—exactly what the patched versions do.

Can static analysis detect brace-expansion DoS?

Yes. Tools like Trivy, Snyk, and npm audit can flag known-vulnerable versions of brace-expansion in your dependency tree. Orbis AppSec uses Trivy to detect and automatically fix such vulnerabilities.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #425

Related Articles

high

How Dependabot Missing Cooldown happens in GitHub Actions and how to fix it

A high-severity misconfiguration in `.github/dependabot.yml` left this Node.js library without a cooldown period, meaning Dependabot would immediately propose updates to newly published packages — including potentially malicious or unstable ones. The fix adds a `cooldown` block with `default-days: 7` to both the `npm` and `github-actions` package ecosystem entries, introducing a mandatory 7-day waiting period before any new package version is surfaced as an update candidate.

critical

How CSRF Protection Failures Happen in FastAPI and How to Fix Them

A critical CORS misconfiguration in `backend/main.py` allowed cookies to be sent alongside wildcard-origin requests, violating the CORS specification and opening the door to cross-site request forgery attacks. The fix conditionally disables `allow_credentials` when the allowed origins list contains a wildcard, bringing the configuration into compliance with browser security rules. This change closes a subtle but dangerous gap that could have let attackers on sibling subdomains forge authenticate

critical

How Missing Rate Limiting Happens in Node.js SSE Handlers and How to Fix It

A critical missing rate-limiting control in `src/sse/handlers/chat.js` allowed any caller to flood the SSE chat endpoint with unlimited requests, risking server resource exhaustion, denial of service, and runaway AI provider API costs. The fix introduces a per-IP sliding-window rate limiter that caps requests at 60 per minute and returns HTTP 429 on violations. Because the endpoint was publicly reachable and only validated API keys — not request frequency — exploitation required nothing more tha

medium

How Denial of Service via Catastrophic Backtracking happens in Node.js and how to fix it

CVE-2026-4867 is a Denial of Service vulnerability in path-to-regexp 0.1.12 where malformed URL parameters can trigger catastrophic backtracking in the library's regular expression engine, allowing an attacker to hang or crash a Node.js application with a single crafted request. The fix upgrades path-to-regexp to version 0.1.13, which patches the vulnerable regex patterns. This change was applied via a package-level override to ensure the patched version is used throughout the entire dependency

high

How Denial of Service via Exponential-Time Complexity happens in Node.js and how to fix it

CVE-2026-13149 is a high-severity Denial of Service vulnerability in the `brace-expansion` npm package, where crafted input strings trigger exponential-time processing that can freeze or crash a Node.js application. The fix upgrades `brace-expansion` from `2.0.2` to `2.1.4` and `minimatch` from `5.1.6` to `5.1.9`, along with npm `overrides` to ensure the patched versions are used throughout the entire dependency tree.

critical

How Unrestricted File Upload happens in Node.js/Express and how to fix it

A critical unrestricted file upload vulnerability was discovered in `mainsystem/routes/admin/profile.js`, where the avatar upload endpoint accepted any file type without validation. An authenticated attacker could upload a malicious server-side script to a web-accessible directory and execute arbitrary code on the server. The fix adds MIME type filtering, an allowlist of safe image formats, and a 2 MB file size limit to the multer middleware.