Back to Blog
high SEVERITY7 min read

How Denial of Service via Exponential Regex 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 caused by exponential-time complexity when processing certain brace patterns. Because `brace-expansion` is a transitive dependency present in many Node.js projects' production dependency trees, an attacker who can influence glob patterns or file path inputs can trigger runaway CPU consumption and crash the service. The fix upgrades the package to patched versions (1.1.16, 2.1.2, or 5.0.7) and p

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

Answer Summary

CVE-2026-13149 is a Denial of Service vulnerability (CWE-1333, Inefficient Regular Expression Complexity) in the `brace-expansion` npm package affecting Node.js applications. When the library processes specially crafted brace patterns like `{a,b,c,...}` with deeply nested or repeated alternatives, its expansion algorithm exhibits exponential time complexity, consuming all available CPU and hanging the process. The fix is to upgrade `brace-expansion` to version 1.1.16, 2.1.2, or 5.0.7 and explicitly pin it as a direct dependency in `package.json` so the patched version is resolved across all transitive dependents.

Vulnerability at a Glance

cweCWE-1333
fixUpgrade brace-expansion to 1.1.16 / 2.1.2 / 5.0.7 and pin it as an explicit dependency
riskRemote attacker can hang or crash a web service by supplying a crafted brace pattern
languageJavaScript / Node.js
root causebrace-expansion's expansion algorithm has O(2^n) worst-case complexity on certain inputs
vulnerabilityDenial of Service via Exponential-Time Brace Expansion

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

The Vulnerability at a Glance

Field Detail
Vulnerability Denial of Service via Exponential-Time Brace Expansion
CWE CWE-1333 — Inefficient Regular Expression Complexity
Language JavaScript / Node.js
Risk Remote attacker can hang or crash a web service
Root Cause O(2ⁿ) worst-case expansion complexity in brace-expansion
Fix Upgrade to brace-expansion 1.1.16 / 2.1.2 / 5.0.7

Introduction

This high-severity Denial of Service vulnerability could have allowed any remote attacker to monopolize CPU threads in a production web service — with nothing more than a single HTTP request containing a cleverly crafted string. The culprit is brace-expansion, a ubiquitous Node.js utility that expands shell-style brace patterns like {a,b,c} into arrays of strings. It sits deep in the dependency trees of tools like glob, minimatch, and many others, making it nearly invisible — yet its algorithmic flaw, tracked as CVE-2026-13149, is directly reachable in any application that processes user-influenced file paths, glob patterns, or search strings.

The vulnerable package was present in package-lock.json at version 2.1.1. Because this is a web service where request handlers process user-influenced input, Trivy flagged the pattern as likely exploitable in production.


The Vulnerability Explained

What Does brace-expansion Do?

brace-expansion takes a string like file.{js,ts,jsx,tsx} and returns ['file.js', 'file.ts', 'file.jsx', 'file.tsx']. It is used heavily by glob-matching libraries to enumerate possible file paths. The library is downloaded hundreds of millions of times per week on npm.

The Algorithmic Time Bomb

The vulnerability lies in how brace-expansion handles nested or repeated brace groups. Consider a pattern like:

{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}{a,b}

Each {a,b} doubles the number of combinations. With 10 groups, the library must generate 2¹⁰ = 1,024 strings. With 30 groups, that is over 1 billion strings. The expansion is computed eagerly and synchronously, blocking Node.js's single-threaded event loop entirely.

The vulnerable code path in versions prior to the fix performs a Cartesian-product expansion without any upper bound on output size or time spent. This is a classic CWE-1333 pattern: a function whose execution time grows exponentially with input length.

Pre-Fix State in package-lock.json

Before the fix, package-lock.json resolved brace-expansion to version 2.1.1 through transitive dependencies (notably glob and minimatch). There was no explicit top-level pin, meaning the vulnerable version was silently inherited:

// Before fix — no explicit brace-expansion entry in top-level dependencies
"dependencies": {
  "archiver": "^8.0.0",
  "bcryptjs": "^3.0.3",
  "bullmq": "^5.78.1",
  ...
}

Because brace-expansion@2.1.1 was only a transitive dependency, it was easy to overlook in manual reviews.

Concrete Attack Scenario

This application is a web service. Suppose it exposes an endpoint that accepts a file glob pattern for searching or listing resources — for example:

GET /api/files?pattern={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}

If the server passes req.query.pattern into any function backed by glob or minimatch (which internally calls brace-expansion), the library will attempt to expand 8⁴ = 4,096 strings synchronously. Scale the exponent up slightly and the event loop is blocked for seconds. Send a handful of concurrent requests and the service is effectively down — no authentication required, no special privileges, just an HTTP GET.

Even without a direct glob endpoint, brace-expansion may be invoked indirectly through build tooling, file-watching middleware, or template engines that process user input.


The Fix

What Changed

The fix makes two targeted changes to package.json and package-lock.json:

1. Explicit top-level dependency pin in package.json:

 "dependencies": {
   "archiver": "^8.0.0",
   "bcryptjs": "^3.0.3",
+  "brace-expansion": "^2.1.2",
   "bullmq": "^5.78.1",
   ...
 }

By adding brace-expansion as a direct dependency, npm is forced to resolve it to ^2.1.2 (the patched version) across the entire dependency tree, overriding any transitive request for 2.1.1.

2. Lock file updated to patched versions:

The package-lock.json now resolves brace-expansion to 2.1.2 (and related packages to their updated equivalents). The lock file diff also updates several @emnapi/* packages that were co-resolved during the upgrade:

-"version": "1.2.1",
-"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
-"integrity": "sha512-uTII7OYF+...",
+"version": "1.2.3",
+"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.3.tgz",
+"integrity": "sha512-ELEBe8PsL...",

Why Pinning as a Direct Dependency Matters

npm's dependency resolution algorithm will use the highest satisfying version it finds when multiple packages request the same transitive dependency. However, without an explicit top-level pin, a future npm install could still pull in a vulnerable version if a transitive dependency loosens its own version range. Pinning brace-expansion at the top level guarantees the patched version wins every resolution contest.

What the Patched Versions Fix

Versions 1.1.16, 2.1.2, and 5.0.7 of brace-expansion introduce a complexity guard that limits the number of expansions the algorithm will attempt before throwing an error or returning a safe fallback. This changes the worst-case behavior from O(2ⁿ) unbounded CPU consumption to a fast, bounded failure — exactly the right trade-off for a security fix.


Prevention & Best Practices

1. Audit Transitive Dependencies Regularly

Transitive dependencies are the silent majority of your attack surface. Run npm audit and integrate a dedicated SCA scanner (Trivy, Snyk, Socket.dev) into your CI pipeline so that CVEs in indirect dependencies are caught before they reach production.

# Quick check
npm audit --audit-level=high

# Trivy lock-file scan
trivy fs --scanners vuln package-lock.json

2. Validate and Limit Pattern Inputs

If your application accepts glob patterns or file path expressions from users, impose strict length limits and character allowlists before passing them to any expansion library:

// Example guard before using glob
const MAX_PATTERN_LENGTH = 256;
const SAFE_PATTERN = /^[a-zA-Z0-9_\-\/\.\*\?\[\]{}]+$/;

function safeGlob(pattern) {
  if (typeof pattern !== 'string' || pattern.length > MAX_PATTERN_LENGTH) {
    throw new Error('Invalid pattern');
  }
  if (!SAFE_PATTERN.test(pattern)) {
    throw new Error('Pattern contains unsafe characters');
  }
  return glob.sync(pattern);
}

3. Pin Critical Security Dependencies Explicitly

For packages with a history of algorithmic complexity issues (regex engines, parsers, expanders), add them as explicit top-level dependencies in package.json even if you don't use them directly. This gives you control over the resolved version.

4. Use Lockfile Integrity Checks in CI

Commit package-lock.json to version control and use npm ci (not npm install) in CI/CD pipelines. npm ci enforces the exact lock file, preventing silent version drift.

# In CI — always use npm ci
npm ci --ignore-scripts

5. Reference Security Standards


Key Takeaways

  • Transitive dependencies are in scope for attackers. brace-expansion was never imported directly in application code, yet it was reachable through glob/minimatch and exploitable via user-supplied HTTP request parameters.
  • Exponential-time algorithms are DoS vulnerabilities. Any function that performs Cartesian-product expansion, recursive backtracking, or unbounded iteration on user-controlled input is a potential availability risk, not just a performance concern.
  • Pinning brace-expansion explicitly in package.json is the correct fix. Simply upgrading a transitive dependency in the lock file is fragile; adding it as a direct dependency with ^2.1.2 ensures the patched version wins all future resolution conflicts.
  • A single HTTP request is sufficient to exploit this. No authentication, no special permissions — just a crafted pattern string sent to any endpoint that triggers glob matching.
  • Automated scanning of package-lock.json catches what code review misses. Manual review of application source code would never surface this vulnerability; only SCA tooling scanning the lock file found it.

How Orbis AppSec Detected This

  • Source: User-influenced input (e.g., HTTP request query parameters or body fields) passed as glob pattern strings into libraries backed by brace-expansion.
  • Sink: The brace-expansion package's internal expansion function, invoked transitively through glob / minimatch dependencies listed in package-lock.json.
  • Missing control: No upper bound on expansion complexity; no explicit version pin to force resolution to a patched release.
  • CWE: CWE-1333 — Inefficient Regular Expression Complexity (also CWE-400 — Uncontrolled Resource Consumption).
  • Fix: Added "brace-expansion": "^2.1.2" as an explicit top-level dependency in package.json and regenerated package-lock.json to resolve all instances to the patched version.

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 is a reminder that the most dangerous vulnerabilities in modern Node.js applications often hide not in the code you write, but in the packages your packages depend on. A single transitive dependency — brace-expansion at version 2.1.1 — carried an exponential-time complexity flaw that could bring down an entire web service with a single malformed HTTP request. The fix is precise and non-breaking: upgrade to 2.1.2 and pin it explicitly so the resolution is stable across future installs.

Treat your lock file as a security artifact. Scan it, pin critical packages, and automate the detection of CVEs before they reach production.


References

Frequently Asked Questions

What is a Denial of Service via exponential-time complexity?

It is an attack where a crafted input causes an algorithm to run for an exponentially growing amount of time, consuming CPU or memory until the process becomes unresponsive or crashes.

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

Keep dependencies up to date, pin known-safe versions explicitly, validate and limit the length of any user-supplied glob or pattern strings, and use automated scanners like Trivy or npm audit to catch vulnerable transitive dependencies.

What CWE is exponential-time complexity DoS?

CWE-1333 — Inefficient Regular Expression Complexity (also sometimes categorized under CWE-400, Uncontrolled Resource Consumption).

Is rate-limiting enough to prevent this DoS vulnerability?

Rate-limiting reduces exposure but is not sufficient on its own; a single well-crafted request can still monopolize a CPU thread for seconds or minutes, so patching the library is the only complete fix.

Can static analysis detect this type of vulnerability?

Yes — tools like Trivy, Snyk, and npm audit scan the dependency lock file against known CVE databases and will flag vulnerable versions of brace-expansion before they reach production.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #720

Related Articles

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 dependabot-missing-cooldown happens in GitHub Actions/Node.js and how to fix it

The repository's `.github/dependabot.yml` had no cooldown period configured, meaning Dependabot could immediately propose updates to newly published package versions with zero time for the community to flag malware or instability. The fix adds a `cooldown` block with `default-days: 7` to both the `npm` and `github-actions` ecosystems, forcing a 7-day waiting period before new releases are surfaced as update PRs.

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.

critical

How Remote Code Execution Happens in Handlebars Template Compilation and How to Fix It

CVE-2026-33937 is a critical remote code execution vulnerability in Handlebars.js that allows attackers to execute arbitrary code by passing maliciously crafted Abstract Syntax Tree (AST) objects to the compile() function. The vulnerability was patched in version 4.7.9, and we've upgraded to protect against this threat vector.

critical

How Denial of Service via Gzip Bomb happens in Node.js and how to fix it

A critical Denial of Service vulnerability (CVE-2026-59873) in the `tar` npm package allowed attackers to craft malicious gzip archives that could exhaust memory or CPU during decompression. The fix upgrades `tar` from 7.5.11 to 7.5.21 across `package.json` and `package-lock.json`, closing the resource-exhaustion path without changing any application code.