Back to Blog
high SEVERITY8 min read

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.

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

Answer Summary

CVE-2026-13149 is a high-severity Denial of Service (DoS) vulnerability in the `brace-expansion` npm package (CWE-1333: Inefficient Regular Expression Complexity), affecting Node.js projects that use glob-style pattern matching. When an attacker supplies a specially crafted brace-expansion string, the library's expansion algorithm runs in exponential time, consuming CPU and memory until the process becomes unresponsive. The fix is to upgrade `brace-expansion` to `2.1.4` (or `1.1.16`/`5.0.7` for other major version lines) and `minimatch` to `5.1.9`, and to add npm `overrides` in `package.json` so all transitive dependents receive the patched version.

Vulnerability at a Glance

cweCWE-1333 (Inefficient Regular Expression Complexity / Algorithmic Complexity)
fixUpgrade brace-expansion to 2.1.4 and minimatch to 5.1.9; pin versions with npm overrides
riskAn attacker can send a single malicious string to freeze or crash the server process
languageJavaScript / Node.js
root causeThe brace-expansion algorithm in versions < 2.1.4 has O(2ⁿ) worst-case complexity for nested/repeated brace patterns
vulnerabilityDenial of Service via Exponential-Time Brace Expansion

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 Attacker can freeze or crash the server process with a single string
Root Cause O(2ⁿ) worst-case complexity for nested brace patterns in brace-expansion < 2.1.4
Fix Upgrade to brace-expansion 2.1.4 + minimatch 5.1.9; pin with npm overrides

Introduction

The package-lock.json file is the unsung gatekeeper of your Node.js supply chain — it pins every transitive dependency your application relies on. In this project, that file locked brace-expansion at version 2.0.2, a version now known to contain CVE-2026-13149: a high-severity Denial of Service vulnerability caused by exponential-time algorithmic complexity.

brace-expansion is the library that turns strings like file.{js,ts,mjs} or src/{a,b,c}/{x,y} into their expanded equivalents. It sits inside minimatch, which in turn powers glob matching across a huge swath of the Node.js ecosystem — linters, test runners, build tools, and file watchers all depend on it. When an attacker can influence the string passed to this expansion logic, they can craft input that causes the algorithm to explode in size, grinding the process to a halt.


The Vulnerability Explained

How Brace Expansion Works — and Where It Breaks

Brace expansion is conceptually simple: {a,b}{c,d} expands to ['ac', 'ad', 'bc', 'bd']. The number of results is the product of the sizes of each brace group. That multiplicative relationship is exactly what makes it dangerous.

Consider a string like:

{a,b,c,d,e,f,g,h,i,j}{a,b,c,d,e,f,g,h,i,j}{a,b,c,d,e,f,g,h,i,j}...

Or, more insidiously, deeply nested patterns:

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

In versions of brace-expansion prior to 2.1.4, the expansion algorithm does not guard against these combinatorial explosions. Each additional nesting level or additional comma-separated alternative multiplies the work required. With enough nesting, even a few hundred bytes of input can trigger millions of recursive calls and allocate gigabytes of intermediate strings.

The vulnerable entry in package-lock.json was:

"node_modules/brace-expansion": {
  "version": "2.0.2",
  "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
  "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
  ...
}

And minimatch at 5.1.6 depended directly on this vulnerable version via "brace-expansion": "^2.0.1".

A Concrete Attack Scenario

Suppose your application exposes an endpoint that accepts a glob pattern from the user to search for files or match routes:

const minimatch = require('minimatch');

app.get('/files', (req, res) => {
  const pattern = req.query.pattern; // user-controlled input
  const results = files.filter(f => minimatch(f, pattern));
  res.json(results);
});

An attacker sends a single HTTP request:

GET /files?pattern={a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p}{a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p}{a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p}{a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p}

That single pattern expands to 16⁴ = 65,536 strings — and a few more brace groups pushes it into the millions. The Node.js event loop is single-threaded; while it grinds through this expansion, no other requests are processed. The server becomes unresponsive. This is a classic ReDoS-style attack applied to algorithmic expansion rather than regular expressions.

Even if your application does not directly expose glob matching to users, any dependency in your tree that passes user-influenced paths through minimatch or brace-expansion is a potential vector.


The Fix

What Changed in package-lock.json

The fix upgrades two packages:

Before:

"node_modules/brace-expansion": {
  "version": "2.0.2",
  "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
  "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="
}
"node_modules/minimatch": {
  "version": "5.1.6",
  "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
  "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="
}

After:

"node_modules/brace-expansion": {
  "version": "2.1.4",
  "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz",
  "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg=="
}
"node_modules/minimatch": {
  "version": "5.1.9",
  "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz",
  "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw=="
}

What Changed in package.json

A critical addition was made to package.json — the overrides field:

"overrides": {
  "brace-expansion": "2.1.4",
  "minimatch": "5.1.9"
}

This is the key insight of the fix. Simply upgrading brace-expansion in the top-level node_modules is not enough — other packages in the dependency tree may have their own pinned references to the vulnerable 2.0.2 version. The overrides field (introduced in npm 8.3) forces npm to resolve every occurrence of brace-expansion across the entire dependency graph to 2.1.4, regardless of what individual packages declare as their peer dependency range.

Without overrides, you might upgrade the direct dependency but leave transitive copies of the vulnerable version buried inside nested node_modules directories — a subtle but dangerous gap.

Why minimatch Was Also Updated

minimatch is the primary consumer of brace-expansion in this project's dependency tree. Upgrading minimatch from 5.1.6 to 5.1.9 ensures that the package itself is pulling in the patched brace-expansion, and that any internal behavior changes in minimatch that complement the brace-expansion security fix are also included.

What the Patch Actually Does Inside brace-expansion

The 2.1.4 release of brace-expansion introduces a result-count limit and depth guard inside the expansion algorithm. Before generating the full expanded set, the library now calculates the expected output size. If that size exceeds a safe threshold, the expansion is aborted or capped — preventing the exponential blowup entirely. Valid, reasonably-sized brace expressions continue to work exactly as before.


Prevention & Best Practices

1. Use npm overrides for Transitive Dependency Pinning

When a vulnerability exists in a transitive dependency (a dependency of a dependency), simply running npm update may not fix it. Use overrides in package.json to enforce a minimum safe version:

"overrides": {
  "vulnerable-package": ">=safe-version"
}

For Yarn, use resolutions. For pnpm, use pnpm.overrides.

2. Validate and Sanitize User-Supplied Glob Patterns

If your application accepts glob patterns from users, apply strict validation before passing them to any expansion or matching library:

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

// Limit brace nesting depth and count
const braceCount = (pattern.match(/\{/g) || []).length;
if (braceCount > 5) {
  throw new Error('Too many brace groups in pattern');
}

3. Run Automated Dependency Scanning in CI

Integrate tools like Trivy, npm audit, or Snyk into your CI pipeline. CVE-2026-13149 was detected by Trivy scanning package-lock.json. A pipeline gate that fails on HIGH or CRITICAL findings would have caught this before deployment.

Example GitHub Actions step:

- name: Run Trivy vulnerability scanner
  uses: aquasecurity/trivy-action@master
  with:
    scan-type: 'fs'
    scan-ref: '.'
    exit-code: '1'
    severity: 'HIGH,CRITICAL'

4. Keep package-lock.json in Version Control

Your package-lock.json is not just a build artifact — it is a security document. Committing it ensures that every developer and CI environment uses the exact same (and audited) dependency versions.

5. Understand the Scope of Algorithmic Complexity Attacks

Algorithmic complexity attacks (also called "algorithmic DoS" or "complexity attacks") are distinct from resource exhaustion through volume. A single, small HTTP request can cause unbounded CPU consumption. Standard rate limiting and WAF rules often miss these attacks because the payload is not large. Defense must happen at the library level — which is exactly what the brace-expansion patch provides.

Relevant standards:
- OWASP: Denial of Service Cheat Sheet
- CWE-1333: Inefficient Regular Expression Complexity
- CWE-400: Uncontrolled Resource Consumption


Key Takeaways

  • package-lock.json version 2.0.2 of brace-expansion was the exact vulnerable artifact — upgrading to 2.1.4 closes the CVE-2026-13149 attack surface entirely.
  • Transitive vulnerabilities require overrides — simply upgrading a direct dependency is not enough when the vulnerable package appears deeper in the dependency tree via minimatch's own resolution.
  • A single HTTP request carrying a crafted brace pattern can freeze a Node.js event loop — this is not a high-volume attack, making it especially dangerous and hard to catch with traditional rate limiting.
  • minimatch 5.1.9 was upgraded alongside brace-expansion 2.1.4 — both changes work together; updating only one may leave a residual risk path through the other.
  • Trivy's static scan of package-lock.json was sufficient to detect this — you do not need runtime instrumentation to find this class of vulnerability; dependency manifest scanning is enough.

How Orbis AppSec Detected This

  • Source: The brace-expansion library processes strings that can originate from user-controlled input passed through minimatch-based glob matching anywhere in the application or its dependencies.
  • Sink: The brace expansion algorithm inside node_modules/brace-expansion (version 2.0.2), invoked whenever a pattern string containing { characters is processed — effectively any call to minimatch(file, pattern) where pattern is user-influenced.
  • Missing control: No upper bound on expansion result count or recursion depth was enforced before version 2.1.4; the algorithm would unconditionally attempt to materialize all combinations.
  • CWE: CWE-1333 – Inefficient Regular Expression Complexity (applicable to algorithmic expansion complexity as well as regex).
  • Fix: brace-expansion was upgraded from 2.0.2 to 2.1.4 and minimatch from 5.1.6 to 5.1.9, with npm overrides added to package.json to enforce these versions across the entire dependency tree.

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 security vulnerabilities are not always about memory corruption or injection attacks — sometimes the danger is purely mathematical. The brace-expansion library's O(2ⁿ) worst-case behavior for crafted inputs is a textbook algorithmic complexity vulnerability, and it sits inside one of the most widely used glob-matching stacks in the Node.js ecosystem.

The fix is precise and low-risk: upgrading brace-expansion to 2.1.4 and minimatch to 5.1.9, and using npm overrides to ensure the patched versions propagate through every layer of the dependency tree. Valid inputs continue to work exactly as before — only the malicious edge case is blocked.

The broader lesson is one of supply chain hygiene: your application's security posture is only as strong as its weakest transitive dependency. Automated scanning of package-lock.json and package.json — as demonstrated by Trivy's detection of this exact CVE — is an essential, low-friction control that every Node.js project should have in its CI pipeline.


References

Frequently Asked Questions

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

It is an attack where specially crafted input causes an algorithm to take exponentially longer to complete as input size grows, exhausting CPU or memory and making the service unavailable to legitimate users.

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

Keep dependencies up to date, use npm `overrides` to enforce minimum safe versions across your entire dependency tree, and validate or limit the length/structure of user-supplied glob or pattern strings before passing them to expansion libraries.

What CWE is algorithmic complexity DoS?

CWE-1333 (Inefficient Regular Expression Complexity) is the closest match; it covers cases where input-controlled patterns or strings cause super-linear processing time.

Is input length limiting enough to prevent this vulnerability?

Length limits reduce the risk but are not sufficient on their own — the exponential growth can still exhaust resources with moderately sized inputs. Upgrading to the patched version is the correct fix.

Can static analysis detect this vulnerability?

Yes — tools like Trivy (which flagged this exact issue), npm audit, and Snyk scan dependency manifests for known CVEs and can surface this class of vulnerability automatically.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #150

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

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.

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 on dependency updates, meaning Dependabot could immediately propose upgrades to newly published — potentially malicious or unstable — package versions. The fix adds a `cooldown` block with `default-days: 7` to both the `npm` and `github-actions` ecosystems, introducing a mandatory waiting period before any newly released version is surfaced as an update candidate. Because this project