Back to Blog
high SEVERITY7 min read

How Denial of Service happens in Node.js dependency trees and how to fix it

A high-severity Denial of Service vulnerability (CVE-2026-13149) was discovered in the `brace-expansion` package, a transitive dependency used across many Node.js projects. The flaw allows attackers to trigger exponential-time processing by supplying crafted brace patterns, potentially freezing the application. The fix upgrades `brace-expansion` to patched versions (2.1.2, 1.1.16, 5.0.7) and restructures how nested dependencies resolve the package within the `@sentry/bundler-plugin-core` depende

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

Answer Summary

CVE-2026-13149 is a high-severity Denial of Service vulnerability (CWE-1333) in the `brace-expansion` npm package, affecting Node.js applications that process untrusted glob or brace-pattern strings. The vulnerability arises from exponential-time complexity when parsing certain malformed brace expressions, allowing an attacker to freeze the event loop with a small input. The fix upgrades `brace-expansion` to versions 2.1.2, 1.1.16, or 5.0.7 and restructures the `package-lock.json` dependency tree so that `@sentry/bundler-plugin-core/node_modules/minimatch` resolves to the patched version instead of the vulnerable 5.0.6 build.

Vulnerability at a Glance

cweCWE-1333 (Inefficient Regular Expression Complexity)
fixUpgraded `brace-expansion` to 2.1.2 (under `@sentry/bundler-plugin-core/node_modules/minimatch`) and removed the vulnerable 5.0.6 nested resolution
riskAttacker-controlled input can freeze the Node.js event loop, making the application unresponsive
languageJavaScript / Node.js
root causeThe `brace-expansion` package used an algorithm with exponential time complexity when parsing deeply nested or malformed brace patterns
vulnerabilityDenial of Service via exponential-time brace expansion

How Denial of Service Happens in Node.js Dependency Trees and How to Fix It

The package-lock.json file in any non-trivial Node.js project is a sprawling graph of direct and transitive dependencies — and buried inside that graph, a single vulnerable package version can expose your entire application to attack. This post walks through exactly how CVE-2026-13149 works in the brace-expansion package, how it was hiding inside the @sentry/bundler-plugin-core subtree, and what the fix looks like at the lock-file level.


The Vulnerability Explained

What Is brace-expansion and Why Does It Matter?

brace-expansion is a small but widely-used npm package that implements POSIX brace expansion for glob patterns — the same syntax you use when you write src/{components,pages}/**/*.ts in a build tool. It is a dependency of minimatch, which is in turn a dependency of glob, which is used by virtually every build tool, linter, and bundler in the Node.js ecosystem.

Because it sits so deep in the dependency tree, most developers never think about it. That invisibility is exactly what makes CVE-2026-13149 dangerous.

The Root Cause: Exponential-Time Complexity

The vulnerability is classified under CWE-1333 (Inefficient Regular Expression Complexity), but it is not strictly a regex issue — it is an algorithmic complexity problem in how brace patterns are parsed and expanded.

Consider a brace expression like:

{a,b}{c,d}{e,f}{g,h}{i,j}{k,l}{n,m}{o,p}{q,r}{s,t}

Expanding this legitimately produces 2¹⁰ = 1,024 combinations. That is fine. But in the vulnerable versions of brace-expansion, certain malformed or deeply nested inputs cause the expansion to grow exponentially in processing time, not just in output size. An attacker can craft an input that takes milliseconds to type but seconds — or minutes — to process.

In a Node.js application, the event loop is single-threaded. If a synchronous call to brace-expansion blocks for even a few seconds, the entire server becomes unresponsive to all other requests. This is a classic Denial of Service via algorithmic complexity, sometimes called a "Billion Laughs"-style attack.

The Vulnerable Code Path in This Repository

The Trivy scanner identified that the project was resolving brace-expansion version 5.0.6 through the following path:

@sentry/bundler-plugin-core
  └── glob@13.0.6
        └── minimatch
              └── brace-expansion@5.0.6   VULNERABLE
                    └── balanced-match@4.0.4

The package-lock.json contained a dedicated nested resolution for this path:

"node_modules/@sentry/bundler-plugin-core/node_modules/brace-expansion": {
  "version": "5.0.6",
  "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
  "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU...",
  "license": "MIT",
  "dependencies": {
    "balanced-match": "^4.0.2"
  },
  "engines": {
    "node": "18 || 20 || >=22"
  }
}

This explicit nested entry meant that even if the top-level brace-expansion was patched, this subtree would continue using version 5.0.6 — the vulnerable one.

Attack Scenario

Imagine a scenario where your Next.js application uses a Server Action that accepts a file glob pattern from the user (e.g., to preview matching files in a project template). Under the hood, that pattern is passed to a glob call, which internally uses minimatch, which uses brace-expansion. An attacker submits a crafted pattern like:

{{{{{{{{{{{{{{{{{{{{a,b},c},d},e},f},g},h},i},j},k},l},m},n},o},p},q},r},s},t},u}

The vulnerable brace-expansion@5.0.6 begins expanding this and never finishes in any reasonable time. The Node.js event loop freezes. Every other request to the server — including health checks — times out. The application is effectively down.


The Fix

What Changed in package-lock.json

The fix makes two coordinated changes to the lock file:

Removed — the top-level nested brace-expansion@5.0.6 and its companion balanced-match@4.0.4 under @sentry/bundler-plugin-core:

-    "node_modules/@sentry/bundler-plugin-core/node_modules/balanced-match": {
-      "version": "4.0.4",
-      ...
-    },
-    "node_modules/@sentry/bundler-plugin-core/node_modules/brace-expansion": {
-      "version": "5.0.6",
-      ...
-      "dependencies": {
-        "balanced-match": "^4.0.2"
-      },
-    },

Added — a more deeply scoped resolution under minimatch specifically, pinning to the patched brace-expansion@2.1.2 with balanced-match@1.0.2:

+    "node_modules/@sentry/bundler-plugin-core/node_modules/minimatch/node_modules/balanced-match": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+      "license": "MIT"
+    },
+    "node_modules/@sentry/bundler-plugin-core/node_modules/minimatch/node_modules/brace-expansion": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz",
+      ...
+    },

Why This Specific Structure?

The new layout scopes the patched brace-expansion@2.1.2 directly under minimatch's own node_modules directory. This is more precise than the previous approach: instead of overriding brace-expansion for all of @sentry/bundler-plugin-core, it targets exactly the minimatch package that was consuming the vulnerable version. The balanced-match companion is also downgraded from 4.0.4 to 1.0.2, which is the version compatible with the 2.x series of brace-expansion.

The result is a dependency tree where:

@sentry/bundler-plugin-core
  └── glob@13.0.6
        └── minimatch
              ├── node_modules/balanced-match@1.0.2   PATCHED
              └── node_modules/brace-expansion@2.1.2  PATCHED

Version 2.1.2 of brace-expansion includes a fix that bounds the expansion algorithm to polynomial time, eliminating the exponential blowup for malformed inputs.


Prevention & Best Practices

1. Run npm audit in CI

Add npm audit --audit-level=high as a required step in your CI pipeline. This catches known-vulnerable transitive dependencies before they reach production.

npm audit --audit-level=high

2. Use Lock-File Overrides for Deep Transitive Fixes

When a vulnerable package is buried deep in a dependency tree and you cannot wait for upstream maintainers to update, use npm's overrides field in package.json:

{
  "overrides": {
    "brace-expansion": ">=2.1.2"
  }
}

This forces all resolutions of brace-expansion — regardless of depth — to use a version satisfying the constraint.

3. Scan with Trivy or Snyk Regularly

Trivy (the scanner that caught this issue) can be run locally or in CI:

trivy fs --scanners vuln .

It inspects package-lock.json and flags vulnerable transitive dependencies by CVE ID.

4. Avoid Passing Untrusted Input to Glob Functions

Even with patched libraries, treat glob patterns from user input as untrusted. Validate or sanitize them before passing to glob, minimatch, or any brace-expansion-consuming function:

// Bad: passing raw user input
const files = await glob(req.body.pattern);

// Better: validate against an allowlist of safe characters
const SAFE_GLOB = /^[a-zA-Z0-9/_\-.*{}?,\[\]]+$/;
if (!SAFE_GLOB.test(req.body.pattern)) {
  throw new Error('Invalid glob pattern');
}
const files = await glob(req.body.pattern);

5. Security Standards

  • OWASP A06:2021 – Vulnerable and Outdated Components: This vulnerability is a textbook example of why dependency hygiene matters.
  • CWE-1333: Inefficient Regular Expression Complexity — applicable to any algorithmic complexity attack, not just regex.

Key Takeaways

  • brace-expansion@5.0.6 inside @sentry/bundler-plugin-core's subtree was the specific vulnerable instance — a reminder that the same package can appear multiple times in a lock file at different versions, and each instance must be audited.
  • Removing the broad @sentry/bundler-plugin-core/node_modules/brace-expansion override and replacing it with a scoped minimatch-level override is a more surgical and maintainable fix than blanket version pinning.
  • Transitive dependencies are attack surface. A package you never import directly can still be the vector for a production outage.
  • balanced-match version matters too — the brace-expansion@2.x series requires balanced-match@1.0.2, not 4.0.4, and getting this pairing wrong would break the fix.
  • Algorithmic complexity attacks require no authentication. Any endpoint that processes user-supplied strings through a vulnerable code path is exposed.

How Orbis AppSec Detected This

  • Source: Untrusted glob or file-pattern strings entering the application (e.g., via HTTP request bodies processed by Next.js Server Actions or build-tool APIs).
  • Sink: The brace-expansion expansion algorithm invoked transitively through minimatchglob@sentry/bundler-plugin-core, as resolved by node_modules/@sentry/bundler-plugin-core/node_modules/brace-expansion@5.0.6.
  • Missing control: No upper bound on expansion complexity; the vulnerable version lacked protection against exponential-time inputs.
  • CWE: CWE-1333 — Inefficient Regular Expression Complexity.
  • Fix: Replaced the brace-expansion@5.0.6 nested resolution under @sentry/bundler-plugin-core with a more precisely scoped brace-expansion@2.1.2 entry under minimatch's own node_modules, paired with the compatible balanced-match@1.0.2.

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 your application's security posture is only as strong as its deepest transitive dependency. The brace-expansion package is invisible to most developers — it appears nowhere in their own code — yet a single vulnerable version nested inside @sentry/bundler-plugin-core was enough to expose the entire application to a Denial of Service attack.

The fix is precise: remove the broad nested override for brace-expansion@5.0.6, introduce a scoped resolution for brace-expansion@2.1.2 directly under minimatch, and pair it with the correct balanced-match@1.0.2. This surgical approach ensures the vulnerable code path is eliminated without disrupting other parts of the dependency graph.

Make dependency scanning a first-class citizen of your CI pipeline. Run npm audit, integrate Trivy, and use overrides in package.json when you need to force a safe version across the entire tree. The cost of prevention is a few minutes of configuration; the cost of a production DoS is measured in downtime, reputation, and revenue.


References

Frequently Asked Questions

What is a brace-expansion Denial of Service vulnerability?

It is a flaw where specially crafted brace patterns (e.g., `{a,{b,{c,...}}}`) cause the expansion algorithm to run in exponential time, consuming all CPU and blocking the Node.js event loop.

How do you prevent brace-expansion DoS in Node.js?

Keep `brace-expansion` updated to patched versions (≥2.1.2 or ≥1.1.16), audit transitive dependencies regularly with `npm audit`, and avoid passing untrusted strings to glob or minimatch functions.

What CWE is brace-expansion DoS?

CWE-1333 — Inefficient Regular Expression Complexity (also known as ReDoS or algorithmic complexity attacks).

Is input validation alone enough to prevent brace-expansion DoS?

Not reliably. While limiting input length reduces risk, the safest fix is patching the library itself, because the exponential behavior can be triggered with relatively short inputs in vulnerable versions.

Can static analysis detect brace-expansion DoS?

Yes. Tools like Trivy, Snyk, and `npm audit` can flag known-vulnerable versions of `brace-expansion` in your dependency tree, even when the package is a transitive dependency.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #1927

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

high

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.

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.