Back to Blog
high SEVERITY5 min read

How Denial of Service via Exponential-Time Complexity Happens in Node.js Dependencies and How to Fix It

A high-severity Denial of Service vulnerability (CVE-2026-13149) was discovered in the brace-expansion npm package, where maliciously crafted input could trigger exponential-time complexity and crash Node.js applications. The fix upgrades brace-expansion from version 5.0.6 to 5.0.9 using npm overrides to ensure all nested dependencies receive the patched version.

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

Answer Summary

CVE-2026-13149 is a Denial of Service vulnerability in the brace-expansion npm package (CWE-1333: Inefficient Regular Expression Complexity) affecting Node.js applications. Attackers can craft malicious brace patterns that cause exponential processing time, leading to application hangs or crashes. The fix involves upgrading brace-expansion to version 5.0.9 or later using npm overrides in package.json to ensure all transitive dependencies use the patched version.

Vulnerability at a Glance

cweCWE-1333
fixUpgrade brace-expansion to 5.0.9 via npm overrides
riskApplication hang or crash from malicious input patterns
languageJavaScript/Node.js
root causeExponential-time complexity in brace expansion parsing algorithm
vulnerabilityDenial of Service (ReDoS/Algorithmic Complexity)

Introduction

In this repository's dependency tree, Trivy flagged a high-severity vulnerability lurking in package-lock.json: the brace-expansion package at version 5.0.6 contained CVE-2026-13149, an algorithmic complexity flaw that could bring down a Node.js application with a single malicious input string.

The brace-expansion package is a foundational utility used by glob pattern matching libraries like minimatch and micromatch. It expands brace patterns like {a,b,c} into arrays ['a', 'b', 'c']. This functionality appears everywhere—from build tools to file system operations—making this vulnerability particularly concerning given its position deep in most Node.js dependency trees.

Looking at the package-lock.json, we can see the vulnerable version pinned:

"node_modules/brace-expansion": {
  "version": "5.0.6",
  "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
  "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",

This version contained the exponential-time complexity bug that CVE-2026-13149 addresses.

The Vulnerability Explained

What is Exponential-Time Complexity?

The brace-expansion library parses patterns like {1..5} or {a,b,c} and expands them into arrays. However, version 5.0.6 and earlier contained an algorithm that, when given specially crafted nested brace patterns, would exhibit exponential time complexity.

Consider a pattern like {a{b{c{d{e{f{g{h{i{j}}}}}}}}}. Each level of nesting multiplies the processing time. An attacker could craft a pattern where each additional character doubles (or worse) the computation time, creating what's known as a "billion laughs" style attack.

The Attack Vector

Here's how an attacker could exploit this:

  1. Identify an input path: Any application feature that uses glob patterns, file matching, or brace expansion with user-controlled input becomes a target
  2. Craft a malicious pattern: Create a deeply nested or specially structured brace pattern
  3. Submit the payload: Send the pattern through an API endpoint, file upload name, or configuration input
  4. Cause resource exhaustion: The server's CPU spikes to 100% processing the expansion, blocking the event loop and making the application unresponsive

For example, if this application uses minimatch (which depends on brace-expansion) to validate file paths or process user-provided glob patterns, an attacker could submit:

{a{b{c{d{e{f{g{h{i{j{k{l{m{n{o{p}}}}}}}}}}}}}}}

This single string could lock up the Node.js process for minutes or hours, effectively creating a Denial of Service.

Real-World Impact

Since brace-expansion sits deep in the dependency tree (often pulled in by glob, minimatch, or build tools), the vulnerable code path may be exercised in unexpected places:

  • Build systems: Processing user-provided file patterns
  • File upload handlers: Validating or filtering filenames
  • API endpoints: Any route accepting glob-style patterns
  • Configuration parsers: Reading user-provided config files

The Fix

The fix involves two coordinated changes to ensure the vulnerable version is completely replaced throughout the dependency tree.

Before (Vulnerable)

package-lock.json:

"node_modules/brace-expansion": {
  "version": "5.0.6",
  "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
  "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
  ...
  "engines": {
    "node": "18 || 20 || >=22"
  }
}

package.json:

"overrides": {
  "tar": "7.5.21"
}

After (Fixed)

package-lock.json:

"node_modules/brace-expansion": {
  "version": "5.0.9",
  "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
  "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
  ...
  "engines": {
    "node": "20 || >=22"
  }
}

package.json:

"overrides": {
  "tar": "7.5.21",
  "brace-expansion": "5.0.9"
}

Why npm Overrides?

The critical addition is the overrides entry in package.json:

"overrides": {
  "tar": "7.5.21",
  "brace-expansion": "5.0.9"
}

This is essential because brace-expansion is typically a transitive dependency—it's not directly listed in your dependencies, but pulled in by other packages like glob or minimatch. Without the override, npm might still install the vulnerable version to satisfy another package's version requirements.

The overrides field tells npm: "Regardless of what version other packages request, always use version 5.0.9 of brace-expansion." This ensures complete remediation across the entire dependency tree.

Additional Change: @capacitor/core

The diff also shows a small change to @capacitor/core:

-      "peer": true,

This removes the peer designation, ensuring the package is installed directly rather than relying on peer dependency resolution. This change helps stabilize the dependency tree and ensures consistent version resolution.

Key Takeaways

  • Transitive dependencies are attack surface: brace-expansion wasn't a direct dependency, yet it created a high-severity vulnerability in the application
  • npm overrides are essential for complete remediation: Simply running npm update may not fix transitive dependencies—use overrides to force specific versions
  • Algorithmic complexity attacks don't require authentication: A single malicious string can DoS an application without any credentials
  • The fix narrowed Node.js version support: Version 5.0.9 dropped Node 18 support ("node": "20 || >=22"), which may require consideration for legacy deployments
  • Defense in depth matters: Even with patched libraries, validate and limit the complexity of user-provided patterns

How Orbis AppSec Detected This

  • Source: The brace-expansion package version 5.0.6 in the dependency tree, potentially receiving user-influenced input through glob pattern processing
  • Sink: The brace expansion algorithm in brace-expansion/index.js that processes nested brace patterns
  • Missing control: No complexity limits on the expansion algorithm, allowing exponential-time processing
  • CWE: CWE-1333 (Inefficient Regular Expression Complexity) / CWE-400 (Uncontrolled Resource Consumption)
  • Fix: Upgraded brace-expansion to version 5.0.9 via npm overrides to ensure all transitive dependencies use the patched version with optimized algorithm

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-13149 in brace-expansion demonstrates how a vulnerability in a small utility package can have outsized impact due to its position in the npm ecosystem. The exponential-time complexity bug could turn a simple string into a weapon capable of bringing down production servers.

The fix—upgrading to version 5.0.9 and using npm overrides—ensures complete remediation across the dependency tree. But beyond this specific CVE, this incident reinforces the importance of continuous dependency monitoring, understanding your transitive dependencies, and implementing input validation as defense in depth.

Keep your dependencies updated, audit regularly, and remember: security is everyone's responsibility.

Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #6

Related Articles

critical

deleteNestedProperty Prototype Pollution via Dot-Notation Path

The `deleteNestedProperty` function in propertyUtils.ts allowed attackers to manipulate JavaScript object prototypes by passing specially crafted dot-notation paths like `__proto__.polluted`. A fix now blocks dangerous keys before processing, preventing prototype pollution attacks that could affect all objects in the application.

high

How Denial of Service via Infinite Loop Happens in JavaScript Dependencies and How to Fix It

CVE-2026-67213 is a high-severity denial of service vulnerability in nanoid before version 5.1.6 that triggers an infinite loop during random ID generation when processing specially crafted input. We upgraded nanoid across the entire dependency tree to patch this flaw and prevent attackers from freezing application threads. This fix ensures that ID generation remains resilient even when handling adversarial input patterns.

high

How Sensitive Data Exposure happens in Zotero plugins and how to fix it

A high-severity data exposure vulnerability in `Zotero.ts` automatically transmitted complete document metadata—including private notes, attachment paths, and tags—to external LLM services without user consent. The fix replaces broad `item.toJSON()` serialization with explicit field selection, sending only essential bibliographic data.

high

How missing dependency update cooldowns happen in GitHub Dependabot configurations and how to fix it

A semgrep scan flagged `.github/dependabot.yml` for lacking a cooldown period, meaning Dependabot would immediately propose updates to brand-new package versions across npm, Bundler, and Docker ecosystems. The fix adds a `cooldown: default-days: 7` block to every `package-ecosystem` entry, forcing a one-week waiting period before newly published releases are considered — reducing exposure to malicious or unstable package drops.

high

How Path Traversal Happens in TensorFlow's Data Service and How to Fix It

TensorFlow's data service dispatcher validated dataset IDs against forward-slash traversal attacks but overlooked backslash characters on non-Windows platforms, allowing attackers to escape the root directory. A targeted fix adds explicit backslash validation across all platforms, closing a high-severity path traversal vulnerability in the snapshot management system.

critical

How Unbounded WebSocket Message Handling Causes Resource Exhaustion in Node.js and How to Fix It

The WebSocketCrossServerAdapter class in a popular Node.js WebSocket library lacked any rate limiting on inbound messages, allowing attackers to flood Redis nodes and WebSocket servers with high-volume traffic. The fix introduces a configurable `rateLimit` option that caps messages per connection per second, preventing resource exhaustion while preserving legitimate functionality.