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.

Prevention & Best Practices

1. Regular Dependency Auditing

Run security audits as part of your CI/CD pipeline:

npm audit
npx trivy fs --scanners vuln .

2. Use Lock Files and Overrides Strategically

  • Always commit package-lock.json to version control
  • Use overrides (npm) or resolutions (yarn) to force secure versions of transitive dependencies
  • Review your lock file changes in PRs

3. Input Validation

Even with patched dependencies, implement defense in depth:

// Limit pattern complexity before passing to glob/minimatch
function validateGlobPattern(pattern) {
  const maxLength = 200;
  const maxNestingDepth = 5;

  if (pattern.length > maxLength) {
    throw new Error('Pattern too long');
  }

  const nestingDepth = (pattern.match(/{/g) || []).length;
  if (nestingDepth > maxNestingDepth) {
    throw new Error('Pattern too complex');
  }

  return pattern;
}

4. Monitor for New CVEs

Subscribe to security advisories:
- GitHub Dependabot alerts
- npm security advisories
- Snyk vulnerability database

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.

References

Frequently Asked Questions

What is algorithmic complexity denial of service?

It's a vulnerability where specially crafted input causes an algorithm to consume excessive CPU time or memory, making the application unresponsive or crashing it entirely.

How do you prevent algorithmic complexity DoS in Node.js?

Keep dependencies updated, use npm audit regularly, implement input validation and length limits, and use npm overrides to force secure versions across transitive dependencies.

What CWE is algorithmic complexity DoS?

CWE-1333 (Inefficient Regular Expression Complexity) or CWE-400 (Uncontrolled Resource Consumption) depending on the specific implementation.

Is input length validation enough to prevent algorithmic complexity DoS?

Not always—while length limits help, some algorithms have exponential complexity regardless of input length. The safest approach is using patched library versions with fixed algorithms.

Can static analysis detect algorithmic complexity DoS?

Yes, tools like Trivy, npm audit, and Snyk can detect known vulnerable package versions. Some advanced tools can also identify complexity issues in custom code through pattern analysis.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #6

Related Articles

high

How Denial of Service via infinite loop happens in Node.js dependencies and how to fix it

A high-severity Denial of Service vulnerability in the nanoid package (CVE-2026-67213) was discovered in the project's dependency tree, where crafted input could trigger an infinite loop during random ID generation. The fix upgrades nanoid from 3.3.17 to 3.3.18 and adds an npm override to ensure all transitive dependencies use the patched version.

high

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

A Dependabot configuration in `.github/dependabot.yml` was missing cooldown periods for both its npm and GitHub Actions package ecosystems, meaning newly published — potentially malicious or unstable — package versions could be proposed for adoption immediately after release. Adding a `cooldown` block with `default-days: 7` to each ecosystem entry creates a 7-day buffer, allowing the security community time to identify and flag compromised packages before they reach your codebase.

high

How pnpm Missing Minimum Release Age happens in Node.js workspaces and how to fix it

A missing `minimumReleaseAge` setting in `pnpm-workspace.yaml` left this Node.js workspace vulnerable to immediately installing newly published — potentially malicious — package versions. The fix adds `minimumReleaseAge: 10080` (7 days in minutes) to enforce a quarantine window before any freshly published package can be installed. This single configuration change significantly reduces the risk of supply chain attacks targeting the package publishing pipeline.

high

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

A high-severity misconfiguration in `.github/dependabot.yml` left three `package-ecosystem` entries without a cooldown period, meaning Dependabot could immediately propose updates from newly published—potentially malicious—packages. The fix adds a `cooldown` block with `default-days: 7` to each entry, introducing a mandatory waiting period before any newly released package version is surfaced as an update candidate. For a Node.js library whose vulnerabilities ripple downstream to all consumers,

critical

How Unauthenticated Proxy Endpoints Enable DoS Amplification in FastAPI and how to fix it

Public proxy endpoints in `backend/api/proxy.py` had no rate limiting, allowing any attacker to flood the httpx connection pool with unauthenticated requests and amplify denial-of-service attacks against downstream tile and coordinate-conversion services. The fix introduces a per-IP sliding-window rate limiter using environment-configurable thresholds, closing the amplification vector without breaking legitimate usage.

high

How command injection happens in Node.js child_process calls and how to fix it

A high-severity command injection risk was discovered in `npm/holidaytw/lib/installer.js` where the `verifyBinaryExecutes` function passed a user-influenced `binPath` argument directly to `spawnSync` without sanitization. The fix replaces `spawnSync` with `execFileSync` combined with `path.resolve()` and explicit `shell: false`, eliminating the shell interpretation attack surface. This proactive hardening raises the bar against automated exploit-chaining tools even in local CLI contexts.