Back to Blog
high SEVERITY8 min read

How Denial of Service via Exponential 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 processing of specially crafted brace patterns. The vulnerability was discovered in `cdk-eregs/package-lock.json` and fixed by upgrading to patched versions (1.1.16, 2.1.2, and 5.0.7+) via an npm `overrides` directive. Left unpatched, an attacker who can influence brace-pattern inputs could freeze or crash Node.js processes with a surprisingly small malicious string.

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

Answer Summary

CVE-2026-13149 is a Denial of Service vulnerability (CWE-1333) in the `brace-expansion` npm package, affecting versions prior to 1.1.16, 2.1.2, and 5.0.7. The root cause is a regular expression or recursive expansion algorithm with exponential time complexity when processing deeply nested or repeated brace patterns like `{a,b}{a,b}{a,b}...`. The fix is to upgrade `brace-expansion` to a patched version—either directly or via an npm `overrides` field in `package.json`—which caps or linearizes the expansion logic to prevent runaway processing.

Vulnerability at a Glance

cweCWE-1333 (Inefficient Regular Expression Complexity)
fixUpgrade brace-expansion to 5.0.9 (pinned via npm overrides) which bounds expansion complexity
riskAn attacker can crash or freeze a Node.js process with a small crafted input string
languageJavaScript / Node.js
root causebrace-expansion processes nested/repeated brace patterns in exponential time, producing combinatorial output
vulnerabilityDenial of Service via exponential-time brace expansion

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


Vulnerability at a Glance

Field Detail
CVE CVE-2026-13149
Severity High
CWE CWE-1333 – Inefficient Regular Expression Complexity
Package brace-expansion (npm)
Fixed in 1.1.16 / 2.1.2 / 5.0.7+
Affected file cdk-eregs/package-lock.json

Introduction

The cdk-eregs/package-lock.json file locks the entire transitive dependency tree for an AWS CDK infrastructure project. Buried several layers deep in that tree was brace-expansion, a utility that turns shell-style brace patterns like file.{js,ts,css} into lists of strings. It is a foundational package—pulled in by glob, minimatch, and dozens of other widely used tools—which makes it both invisible to most developers and dangerous when it misbehaves.

Trivy's dependency scanner flagged brace-expansion in this repository because the version in use contained a flaw that allows exponential-time processing of crafted brace patterns. An attacker who can influence the strings passed to any code path that eventually calls brace-expansion—even indirectly through a glob or file-watcher—can cause the Node.js event loop to hang indefinitely, effectively taking down the process.


The Vulnerability Explained

What is brace expansion?

Brace expansion converts a compact pattern into a list of strings:

// Input
'{a,b,c}.{js,ts}'

// Output
['a.js', 'a.ts', 'b.js', 'b.ts', 'c.js', 'c.ts']

This is useful for glob matching, file discovery, and CLI tooling. The problem arises when the expansion algorithm is applied to nested or repeated brace groups.

The exponential growth problem

Consider what happens with repeated nested alternatives:

{a,b}{a,b}{a,b}  →  8 strings   (2³)
{a,b}{a,b}...×10 →  1,024 strings (2¹⁰)
{a,b}{a,b}...×30 →  1,073,741,824 strings (2³⁰)

A 60-character input string produces over one billion expansion results. The vulnerable versions of brace-expansion attempt to generate the full Cartesian product in memory before returning, meaning both CPU time and memory consumption grow exponentially with the number of brace groups.

The specific algorithmic pattern that causes this is a recursive Cartesian-product expansion without any bound on output size or recursion depth. Internally, the library builds the result by calling something equivalent to:

// Simplified pseudocode of the vulnerable pattern
function expand(pattern) {
  const parts = parse(pattern); // splits on commas and nested braces
  return cartesianProduct(parts.map(expand)); // unbounded recursion + product
}

Each level of nesting multiplies the output size by the number of alternatives at that level. There is no guard that says "stop if output exceeds N items."

What does the vulnerable code look like in context?

The cdk-eregs/package-lock.json (before the fix) contained entries like:

"brace-expansion": {
  "version": "1.1.11",
  ...
}

or

"brace-expansion": {
  "version": "2.0.1",
  ...
}

These transitive versions—pulled in by tools like glob and minimatch which are themselves dependencies of CDK tooling—were all below the patched thresholds.

Attack scenario for this repository

The cdk-eregs project is an AWS CDK application. CDK's build and synthesis pipeline uses glob patterns extensively to discover assets, Lambda function bundles, and configuration files. If any part of that pipeline accepts external input that feeds into a glob pattern—such as a CI/CD parameter, an environment variable, or a configuration file read from an S3 bucket—an attacker with write access to that input could inject a pattern like:

{a,b,c,d,e}{a,b,c,d,e}{a,b,c,d,e}{a,b,c,d,e}{a,b,c,d,e}{a,b,c,d,e}

This 42-character string would generate 15,625 expansions (5⁶). Extend it to ten groups and you reach nearly 10 million. The CDK synthesis process—or any Lambda that calls glob with this input—would hang, causing deployment pipelines to time out or Lambda invocations to exhaust their memory limit.

Even without a direct injection path, the vulnerability is still relevant: the scanner assessment notes it is "present in dependency tree, not confirmed reachable," but the attack surface of a CDK project spans build tools, test runners, and local developer environments—all of which run this code.


The Fix

What changed

The fix introduced an npm overrides block in cdk-eregs/package.json:

Before (package.json — no overrides):

{
  "name": "cdk-eregs",
  "dependencies": {
    "fs-extra": "11.3.1",
    "path": "0.12.7",
    "source-map-support": "0.5.21"
  }
}

After (package.json — with override):

{
  "name": "cdk-eregs",
  "dependencies": {
    "fs-extra": "11.3.1",
    "path": "0.12.7",
    "source-map-support": "0.5.21"
  },
  "overrides": {
    "brace-expansion": "5.0.9"
  }
}

The overrides field (introduced in npm v8.3) forces every package in the dependency tree—regardless of what version they declare as their own dependency—to use brace-expansion@5.0.9. This is the canonical way to patch a transitive dependency vulnerability without waiting for every intermediate package to release an update.

Why this specific version?

The patched versions are 1.1.16, 2.1.2, and 5.0.7+. The fix in each adds a guard against combinatorial explosion—either by capping the maximum number of expansions, by detecting pathological patterns early and returning them unexpanded, or by rewriting the expansion loop to avoid building the full product set in memory.

By pinning to 5.0.9, the override ensures the fix is applied even if a transitive dependency specifies ^1.x or ^2.x, since npm's override mechanism replaces the resolved version regardless of the semver range declared by the dependent package.

The package-lock.json impact

After adding the override and running npm install, the package-lock.json is regenerated so that all entries for brace-expansion—regardless of which package required them—resolve to 5.0.9. The lock file is the authoritative record that the scanner (Trivy) reads, so this change directly eliminates the CVE finding.


Prevention & Best Practices

1. Audit transitive dependencies regularly

brace-expansion is a zero-direct-dependency package that almost no project lists explicitly, yet it appears in hundreds of node_modules trees. Run:

npm audit
# or
npx trivy fs . --scanners vuln

in CI on every pull request to catch newly disclosed CVEs before they reach production.

2. Use overrides (npm) or resolutions (Yarn) for transitive fixes

When a vulnerability lives in a transitive dependency you don't control directly, the overrides field is the right tool:

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

For Yarn Berry:

"resolutions": {
  "vulnerable-package": "patched-version"
}

3. Validate and sanitize brace patterns from external sources

If your application accepts glob patterns from users or external configuration, apply a length cap and a nesting-depth check before passing them to any expansion library:

const MAX_PATTERN_LENGTH = 256;
const MAX_BRACE_DEPTH = 3;

function safeBraceExpand(pattern) {
  if (pattern.length > MAX_PATTERN_LENGTH) {
    throw new Error('Pattern too long');
  }
  const depth = (pattern.match(/\{/g) || []).length;
  if (depth > MAX_BRACE_DEPTH) {
    throw new Error('Pattern nesting too deep');
  }
  return braceExpansion(pattern);
}

This is a defense-in-depth measure; upgrading the library is still the primary fix.

4. Pin dependency versions in lock files and commit them

Always commit package-lock.json to version control. This ensures that npm ci in CI/CD uses exactly the versions you tested, and that security scanners like Trivy can read the resolved tree accurately.

5. Reference standards

  • CWE-1333: Inefficient Regular Expression Complexity — the authoritative classification for this class of algorithmic DoS
  • OWASP: Denial of Service Cheat Sheet
  • Node.js Security Best Practices: validate all inputs that flow into pattern-matching or file-system APIs

Key Takeaways

  • brace-expansion is a hidden risk in virtually every Node.js project that uses glob or minimatch—check your lock file, not just your direct dependencies.
  • A 60-character crafted string can generate billions of expansions; the attack payload is trivially small, making it easy to embed in configuration files or CI parameters.
  • The overrides field in package.json is the correct surgical fix for transitive dependency CVEs—it forces the patched version across the entire dependency tree without touching unrelated packages.
  • Trivy correctly identified this in cdk-eregs/package-lock.json even though brace-expansion is not a direct dependency, demonstrating why scanning the full resolved lock file matters more than scanning package.json alone.
  • Patched versions (1.1.16, 2.1.2, 5.0.7+) add complexity bounds to the expansion algorithm; upgrading is preferable to application-level workarounds because the fix is in the right place.

How Orbis AppSec Detected This

  • Source: The cdk-eregs/package-lock.json file, which resolves transitive dependency versions including brace-expansion at a vulnerable version, is read during CDK build and synthesis operations that process file-system glob patterns.
  • Sink: Any call to braceExpansion(pattern) within the resolved brace-expansion module—invoked transitively through globminimatchbrace-expansion—where pattern contains repeated or deeply nested brace groups.
  • Missing control: No upper bound on the number of expansions or recursion depth in the brace-expansion expansion algorithm prior to versions 1.1.16 / 2.1.2 / 5.0.7.
  • CWE: CWE-1333 – Inefficient Regular Expression Complexity (applies broadly to algorithms with super-linear time growth on adversarial inputs).
  • Fix: An npm overrides directive was added to cdk-eregs/package.json pinning brace-expansion to 5.0.9 across the entire dependency tree, and package-lock.json was regenerated to reflect the patched resolution.

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 sharp reminder that the most dangerous vulnerabilities are often the quietest ones. brace-expansion is a tiny utility with no dependencies of its own, yet its presence in nearly every Node.js project's transitive tree makes a flaw in its core algorithm a systemic risk. Exponential-complexity attacks are particularly insidious because the payload is small, the impact is immediate, and the vulnerable code path is rarely something developers think to audit.

The fix here—a single overrides block in package.json—is minimal, surgical, and does not affect any valid input. It is also a template for how to handle transitive dependency CVEs in npm projects generally. Pair it with automated scanning in CI, commit your lock files, and validate pattern inputs at application boundaries, and you have a robust defense against this class of vulnerability.


References

Frequently Asked Questions

What is a Denial of Service via exponential complexity?

It is an attack where a crafted input triggers an algorithm—such as brace expansion or a regex—to run for an exponentially long time, exhausting CPU and stalling the process before completing.

How do you prevent exponential-complexity DoS in Node.js?

Keep dependency versions current, use npm `overrides` or `resolutions` to force patched transitive versions, and validate or limit the length and nesting depth of any user-supplied pattern strings before passing them to expansion libraries.

What CWE is exponential-complexity DoS?

CWE-1333, "Inefficient Regular Expression Complexity," covers algorithms—including non-regex ones—that exhibit super-linear (often exponential) time growth on adversarial inputs.

Is input length limiting alone enough to prevent this DoS?

Not reliably. Exponential growth means even a 30-character pattern like `{a,b}` repeated six times can generate 64 expansions; a 60-character version generates 4 billion. Upgrading to the patched library that bounds complexity is the correct fix.

Can static analysis detect this vulnerability?

Yes. Tools like Trivy, Snyk, and GitHub Dependabot scan `package-lock.json` for known-vulnerable package versions and flag them with their CVE identifiers, as Trivy did here with CVE-2026-13149.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #2226

Related Articles

high

How Denial of Service via Infinite Loop happens in JavaScript (nanoid) and how to fix it

A high-severity denial of service vulnerability (CVE-2026-67213) was discovered in nanoid versions before 5.1.6 and 3.3.18, where the `customAlphabet` function could enter an infinite loop during random ID generation. The fix upgrades the transitive nanoid dependency from 3.3.16 to 3.3.18 using pnpm overrides, ensuring the vulnerable code path is eliminated from the entire dependency tree including PostCSS.

high

How Denial of Service via Unbounded Recursion happens in Python JSON parsing and how to fix it

A high-severity denial of service vulnerability (CVE-2025-67221) was discovered in orjson 3.10.16, where deeply nested JSON documents could trigger unbounded recursion and crash the application. The fix upgrades orjson to version 3.11.6, which implements recursion depth limits to prevent stack exhaustion attacks.

high

How Denial of Service via Infinite Loop happens in JavaScript and how to fix it

CVE-2026-67213 is a high-severity Denial of Service vulnerability in the popular nanoid JavaScript library, where a flaw in the `customAlphabet` random ID generation function could trigger an infinite loop, hanging the Node.js process indefinitely. The fix upgrades nanoid from version 3.3.11 to 3.3.18 (and adds a package-level override to enforce the safe version across the dependency tree) in the client application. Any application using nanoid's custom alphabet feature with attacker-influenced

high

How Denial of Service via Inefficient Route Matching happens in React Router and how to fix it

CVE-2026-55685 is a high-severity Denial of Service vulnerability in React Router (versions prior to 7.18.0) that allows unauthenticated attackers to exhaust server resources through crafted requests to the manifest endpoint. The fix upgrades react-router from 7.16.0 to 8.3.0, which eliminates the inefficient route matching logic and removes the vulnerable `set-cookie-parser` dependency entirely.

critical

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

A critical vulnerability (CVE-2026-59873) in node-tar versions prior to 7.5.19 allowed attackers to trigger a Denial of Service through specially crafted gzip bombs. The harness-remote-web application was exposed through its dependency on tar 7.5.15, which lacked proper decompression ratio validation. Upgrading to tar 7.5.21 in web/package-lock.json implements safeguards against malicious compressed archives.

high

How Regular Expression Denial of Service happens in JavaScript and how to fix it

CVE-2026-33671 is a Regular Expression Denial of Service (ReDoS) vulnerability in the picomatch glob-matching library, triggered by specially crafted extglob patterns that cause catastrophic regex backtracking. The fix upgrades picomatch to version 4.0.4 (with overrides pinning all transitive copies) in the client's dependency tree, eliminating the vulnerable regex evaluation path. Left unpatched, any code path that passes user-influenced glob patterns to picomatch could be weaponized to stall a