Back to Blog
high SEVERITY7 min read

How unbounded brace-expansion DoS happens in Node.js and how to fix it

CVE-2026-14257 is a denial-of-service vulnerability in the brace-expansion library that allows attackers to crash applications through unbounded expansion of brace patterns. By upgrading from version 2.1.2 to 2.1.3 and 5.0.6 to 5.0.8, we eliminated the risk of memory exhaustion attacks targeting glob pattern expansion in build tools and scripts.

O
By Orbis AppSec
Published July 29, 2026Reviewed July 29, 2026

Answer Summary

CVE-2026-14257 is a denial-of-service vulnerability in Node.js's brace-expansion library (CWE-400: Uncontrolled Resource Consumption) caused by unbounded expansion of brace patterns that can exhaust process memory. The fix upgrades brace-expansion to patched versions (2.1.3 and 5.0.8) that implement expansion limits, preventing attackers from crashing applications through specially crafted glob patterns.

Vulnerability at a Glance

cweCWE-400 (Uncontrolled Resource Consumption)
fixUpgrade to brace-expansion 2.1.3 (^2.0.2 range) and 5.0.8 (^5.0.2 range) with built-in expansion limits
riskRemote attackers can crash applications by providing glob patterns that expand into millions of strings, exhausting available memory
languageNode.js/JavaScript
root causebrace-expansion library lacked limits on expansion length, allowing exponential memory consumption
vulnerabilityDenial of Service via Unbounded Brace-Expansion

How Unbounded Brace-Expansion DoS Happens in Node.js and How to Fix It

The Silent Threat in Your Build Pipeline

In the packages/icons-scripts project, a seemingly innocent dependency was silently creating a critical vulnerability. The brace-expansion library, used extensively by glob pattern matching tools throughout the build pipeline, contained a flaw that allowed attackers to crash the entire application through carefully crafted input. This wasn't a complex exploit—it was a simple matter of sending the right string to the wrong function.

The vulnerability lived in yarn.lock, where versions 2.1.2 and 5.0.6 of brace-expansion were locked in place. These versions lacked a critical safeguard: limits on how many strings a brace pattern could expand into. An attacker could provide a pattern like {1..999999999} or nested expansions that would cause the library to attempt generating billions of strings, consuming gigabytes of memory in seconds and crashing the process.

Understanding the Vulnerability: Brace-Expansion Without Boundaries

Brace expansion is a shell feature that expands patterns like {a,b,c} into multiple strings: a b c. It's incredibly useful for glob patterns in build tools, but it's also dangerous when unbounded.

The core problem:

The vulnerable versions of brace-expansion (2.1.2 and 5.0.6) processed brace patterns without any limits on expansion size. Consider this attack vector:

// Vulnerable code path in brace-expansion 2.1.2
const expand = require('brace-expansion');

// Attacker provides this input through a glob pattern
const maliciousPattern = '{1..999999999}';
const result = expand(maliciousPattern); // Attempts to create 999,999,999 strings!

When this pattern is processed, the library attempts to create an array containing nearly 1 billion string elements. Each string consumes memory, and the process quickly exhausts available RAM. The application crashes with an out-of-memory error, creating a perfect denial-of-service condition.

Real-world attack scenario:

In the icons-scripts package, glob patterns are used to process icon files during build time. If an attacker could influence the glob pattern—through a malicious configuration file, environment variable, or package.json field—they could trigger this vulnerability:

# Attacker creates a package with a malicious glob pattern
glob("icons/{1..999999999}/*.svg", callback)
# Process crashes before any icons are processed

The impact extends beyond just the build process. Any tool in the dependency chain that uses brace-expansion becomes a potential attack vector. Since @swc/cli (a Rust-based JavaScript compiler used in this project) depends on brace-expansion, the vulnerability existed in the production build pipeline.

The Fix: Enforcing Expansion Limits

The pull request addressed this vulnerability by upgrading brace-expansion to patched versions that implement hard limits on expansion:

diff --git a/yarn.lock b/yarn.lock
index 12e0b938..062cb151 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1583,20 +1583,20 @@ __metadata:
   linkType: hard

 "brace-expansion@npm:^2.0.2":
-  version: 2.1.2
-  resolution: "brace-expansion@npm:2.1.2"
+  version: 2.1.3
+  resolution: "brace-expansion@npm:2.1.3"
   dependencies:
     balanced-match: "npm:^1.0.0"
-  checksum: 10/0e0f9b0df1f0809e9c326470e022eeb24334ddb54c43b1ac39f1d38f3cbaeb940794de1db826f5491843ac00d7932c43f10350a65ccd51fe7d05da627152f204
+  checksum: 10/72a6d8c6be638a883dc3adfb88a41f2c34130512e0255bb845f6804f0f577b83d2f84247eec33c32b3adedd4849ccc53b84c54a8267594fabc1f8293ebc7ae02
   languageName: node
   linkType: hard

 "brace-expansion@npm:^5.0.2":
-  version: 5.0.6
-  resolution: "brace-expansion@npm:5.0.6"
+  version: 5.0.8
+  resolution: "brace-expansion@npm:5.0.8"
   dependencies:
     balanced-match: "npm:^4.0.2"
-  checksum: 10/a7acf120fefa79e9d7c9c92898114f57c07596a3920197f3c5917e6a628b04220a5f7f9618c30bdd973a6576a32113b99f9c3f1c8245ccc399dd2a9a718d81d8
+  checksum: 10/75d2d2ebc8f5f65156d16706216d23e48f499a1164e4b045e0ca4045d3ce6b16ced11ea2e4c76e3670b6c840980aea9a35e061
   languageName: node
   linkType: hard

What changed:

  1. brace-expansion 2.1.2 → 2.1.3: The patch version update includes a fix that prevents unbounded expansion by implementing a maximum expansion limit. Patterns that would exceed this limit are now rejected or truncated safely.

  2. brace-expansion 5.0.6 → 5.0.8: The newer major version also includes the expansion limit fix, along with improvements to the dependency on balanced-match (upgraded from ^1.0.0 to ^4.0.2), which handles pattern validation more robustly.

  3. Dependency reorganization: The PR also moved @swc/cli from dependencies to devDependencies in package.json, since it's only needed during build time, not at runtime. This reduces the attack surface by excluding it from production installations.

How the fix works:

The patched versions implement a configurable maximum expansion size (typically around 32,000-65,000 strings). When a pattern would expand beyond this limit, the library:

// Fixed behavior in brace-expansion 2.1.3+
const expand = require('brace-expansion');

const maliciousPattern = '{1..999999999}';
const result = expand(maliciousPattern); 
// Returns: error or safely truncated result
// Does NOT crash the process

Instead of attempting to create billions of strings, the library now:
- Detects the expansion would exceed limits
- Throws a controlled error or returns a safe default
- Allows the process to continue gracefully
- Prevents memory exhaustion

Prevention & Best Practices

1. Keep Dependencies Updated

The most critical step is maintaining up-to-date dependencies. Use tools like:
- npm audit to identify known vulnerabilities
- yarn upgrade to update packages safely
- Dependabot or Renovate to automate security updates

# Check for vulnerabilities
npm audit

# Upgrade to latest versions
npm update brace-expansion balanced-match

2. Validate Glob Patterns

Never pass untrusted input directly to glob or brace-expansion functions:

// ❌ UNSAFE: User-controlled input
const pattern = userInput; // Could be '{1..999999999}'
glob(pattern, callback);

// ✅ SAFE: Validate and sanitize
const allowedPatterns = ['*.js', '*.css', '*.html'];
if (!allowedPatterns.includes(pattern)) {
  throw new Error('Invalid glob pattern');
}
glob(pattern, callback);

3. Use Allowlists for Glob Patterns

Restrict glob patterns to a predefined set:

const ALLOWED_GLOBS = {
  'icons': 'icons/**/*.svg',
  'styles': 'styles/**/*.css',
  'scripts': 'src/**/*.js'
};

function safeGlob(key) {
  if (!ALLOWED_GLOBS[key]) {
    throw new Error('Unknown glob key');
  }
  return glob(ALLOWED_GLOBS[key]);
}

4. Implement Resource Limits

Use Node.js process limits to prevent memory exhaustion:

// Set maximum memory usage
const maxMemory = 512 * 1024 * 1024; // 512MB
require('v8').setFlagsFromString(`--max-old-space-size=${maxMemory / 1024 / 1024}`);

5. Monitor Dependency Updates

Subscribe to security advisories:
- Node Security Database
- npm Security Advisories
- GitHub Security Alerts

Key Takeaways

  • Unbounded expansion is exponential: A pattern like {1..N} doesn't create N strings—it can create exponential growth with nested patterns, making attacks devastating.

  • brace-expansion 2.1.2 and 5.0.6 lack safeguards: These specific versions had no limits on expansion size, making them vulnerable to trivial DoS attacks through glob patterns.

  • Patched versions enforce limits: Upgrading to 2.1.3 and 5.0.8 adds hard limits that prevent expansion beyond safe thresholds, eliminating the DoS vector entirely.

  • Supply chain matters: Even though brace-expansion is a transitive dependency (pulled in by @swc/cli), it directly impacts application security. One vulnerable library in your dependency tree can crash your entire build pipeline.

  • Validation alone isn't enough: While input validation helps, the real fix requires the library itself to enforce limits—which is why upgrading is non-negotiable.

How Orbis AppSec Detected This

Source: The vulnerability entered through the yarn.lock file, where the brace-expansion dependency was locked to vulnerable versions (2.1.2 and 5.0.6).

Sink: The dangerous code path exists in any call to brace-expansion functions that process glob patterns without expansion limits. In this project, the sink is the glob pattern processing used by @swc/cli during the build process.

Missing control: The vulnerable versions lacked any mechanism to limit the number of strings generated during expansion. There was no check to prevent patterns like {1..999999999} from attempting to allocate billions of strings.

CWE: CWE-400 (Uncontrolled Resource Consumption / Resource Exhaustion)

Fix: Upgrade brace-expansion from 2.1.2 to 2.1.3 and from 5.0.6 to 5.0.8, which implement hard limits on expansion size to prevent memory exhaustion attacks.

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 demonstrates how even small, utility libraries can pose significant security risks when they lack proper resource constraints. The brace-expansion vulnerability wasn't a complex code injection or authentication bypass—it was a straightforward resource exhaustion flaw that could crash production builds with a single malicious glob pattern.

By upgrading to versions 2.1.3 and 5.0.8, the project eliminated this denial-of-service vector entirely. The fix shows that sometimes the best security practice is simply keeping your dependencies updated. Automated tools like Orbis AppSec make this easier by continuously scanning for known vulnerabilities and submitting patches automatically.

As developers, we must remember that security isn't just about preventing attackers from stealing data—it's also about preventing them from disrupting service. Every dependency in your supply chain is a potential attack surface. Stay vigilant, keep your packages updated, and validate untrusted input at every boundary.


References

Frequently Asked Questions

What is unbounded brace-expansion DoS?

A denial-of-service attack where specially crafted brace patterns (e.g., `{1..999999999}`) cause the brace-expansion library to generate extremely large arrays of strings, consuming all available memory and crashing the process.

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

Keep brace-expansion and its dependency balanced-match updated to patched versions that enforce expansion limits. Additionally, validate and sanitize any user-controlled input before passing it to glob or brace-expansion functions.

What CWE is unbounded brace-expansion DoS?

CWE-400: Uncontrolled Resource Consumption ('Resource Exhaustion'), which covers scenarios where applications fail to limit resource consumption.

Is input validation enough to prevent this attack?

Input validation helps, but it's not sufficient alone. The root fix requires the library itself to enforce expansion limits, which is why upgrading to patched versions is critical.

Can static analysis detect unbounded brace-expansion DoS?

Yes, security scanners like Trivy detect this vulnerability by checking dependency versions against known CVE databases. However, detecting actual DoS patterns in code requires runtime analysis or fuzzing.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #1525

Related Articles

high

How DNS rebinding attacks happen in Node.js MCP and how to fix it

The Model Context Protocol (MCP) TypeScript SDK in version 1.17.1 lacked DNS rebinding protection, leaving applications vulnerable to a sophisticated network-level attack. By upgrading to version 1.24.0, DNS rebinding protection is now enabled by default, preventing attackers from exploiting the SDK's HTTP request handling to access internal resources or bypass security controls.

critical

How arbitrary code execution via injected protobuf definition type fields happens in Node.js and how to fix it

A critical arbitrary code execution vulnerability (CVE-2026-41242) was discovered in protobufjs versions prior to 7.5.5 and 8.0.1, allowing attackers to inject malicious code through crafted protobuf definition type fields. The fix upgrades the dependency from the vulnerable version 6.11.4 to 7.5.5, which properly sanitizes type field inputs during protobuf parsing. This vulnerability is especially dangerous because protobufjs is widely used in Node.js applications for serialization, meaning a s

critical

How unsafe token deserialization happens in Node.js Temml parser and how to fix it

A critical vulnerability in the Temml math library's parser allowed unsafe token deserialization that could lead to remote code execution when processing user-supplied mathematical expressions. The fix adds strict type validation on fetched token properties before use, preventing exploitation of malformed or crafted payloads.

high

How Denial of Service via excessive memory allocation happens in Node.js adm-zip and how to fix it

A high-severity Denial of Service vulnerability (CVE-2026-39244) was discovered in adm-zip versions prior to 0.6.0, affecting the laliga-fantasy-app. Attackers could craft malicious ZIP files that trigger excessive memory allocation, potentially crashing the web service. The fix involved upgrading adm-zip from version 0.5.16 to 0.6.0, which includes proper memory allocation bounds checking.

high

How Server-Sent Events Injection via Unsanitized Newlines happens in Node.js h3 and how to fix it

A high-severity Server-Sent Events (SSE) injection vulnerability (CVE-2026-33128) was discovered in the h3 HTTP framework, where unsanitized newline characters in event stream fields could allow attackers to inject arbitrary SSE messages. The fix upgrades h3 from version 1.15.5 to 1.15.6 in the frontend's dependency tree, ensuring that newline characters are properly sanitized before being written to event streams.

high

How missing Dependabot cooldown happens in GitHub Actions CI/CD and how to fix it

A high-severity configuration vulnerability was discovered in a Node.js library's `.github/dependabot.yml` file where no cooldown period was set for dependency updates. Without a cooldown, Dependabot could immediately propose updates to newly published (and potentially malicious) package versions, exposing downstream consumers to supply chain attacks. The fix adds a `cooldown` block with `default-days: 7` to both the `github-actions` and `npm` package ecosystem entries.