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
•Technically reviewed by Anupam Mediratta•Published August 26, 2026•Reviewed 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.


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 minimatch → glob → @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.


Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #1927

Related Articles

medium

path-to-regexp 0.1.12 DoS: Catastrophic Backtracking on Malformed URL

path-to-regexp version 0.1.12 contains a regular expression engine vulnerability where maliciously crafted URL parameters trigger exponential backtracking, causing CPU exhaustion and service unavailability. Upgrading to version 0.1.13 eliminates the vulnerable pattern from the parsing logic.

critical

CVE-2026-59873: node-tar 7.5.11 DoS via Crafted Gzip Bomb

node-tar versions 7.5.11 through 7.5.18 are vulnerable to a denial-of-service attack through maliciously crafted gzip archives that decompress to disproportionately large sizes. An attacker can exploit this to exhaust memory and CPU resources by submitting a small, highly compressed archive that expands beyond configured limits during extraction.

high

MapManager.get() Race Condition Duplicates API Requests

The MapManager's `get(mapUid, cache)` method used a check-then-act pattern that permitted multiple concurrent requests to pass the cache miss check simultaneously, triggering redundant API calls and risking cache corruption. The fix introduces a `_pending` promise map to deduplicate in-flight fetches for identical map UIDs.

critical

Lampa Desktop Auto-Update Heuristic Bypass: Execution of Unverified

Lampa Desktop's auto-update mechanism downloaded JavaScript and CSS from `raw.githubusercontent.com` using only heuristic validation—file size thresholds and string pattern matching—that attackers could trivially satisfy. The fix introduces cryptographic integrity verification by cross-referencing Git blob hashes from the GitHub Contents API, ensuring downloaded code matches the repository's authoritative state before execution.

high

adm-zip 0.6.0 Preserves SUID Bits From ZIPs: CVE-2026-102282

The `adm-zip` dependency resolved to 0.6.0 in this project's dependency tree, a version affected by CVE-2026-102282: during extraction it applies the Unix permission bits stored in each ZIP entry's external file attributes verbatim, including the setuid (`04000`), setgid (`02000`), and sticky bits. An attacker who controls an archive passed to `extractAllTo()` or `extractEntryTo()` can therefore have the extractor create a setuid binary owned by whatever user the extraction process runs as. The

high

requestInput() Type Confusion: NaN and Object Bypass in JavaScript

The `requestInput()` utility function lacked validation on its `type` parameter and failed to handle `NaN` results from float conversions, creating a type confusion weakness. An attacker could supply malformed inputs that propagate unhandled `NaN` values or unexpected object types through the type system. The fix adds explicit guards against `NaN` type parameters and rejects non-primitive type values.