Back to Blog
medium SEVERITY7 min read

How Denial of Service via Catastrophic Backtracking happens in Node.js and how to fix it

CVE-2026-4867 is a Regular Expression Denial of Service (ReDoS) vulnerability in the `path-to-regexp` package (versions prior to 0.1.13) that allows an attacker to craft malformed URL parameters that cause catastrophic backtracking in the regex engine, effectively hanging the Node.js event loop. The fix upgrades `path-to-regexp` from 0.1.12 to 0.1.13 and pins the version via an `overrides` field in `package.json` to ensure the patched version is used throughout the entire dependency tree. Any Ex

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

Answer Summary

CVE-2026-4867 is a Regular Expression Denial of Service (ReDoS) vulnerability (CWE-1333) in the `path-to-regexp` npm package versions before 0.1.13. When a Node.js application uses `path-to-regexp` to match URL routes, a specially crafted malformed URL parameter can trigger catastrophic backtracking in the underlying regular expression engine, causing the event loop to stall and effectively denying service to all other users. The fix is to upgrade `path-to-regexp` to version 0.1.13 and, if the package is a transitive dependency, add an `overrides` entry in `package.json` to force the patched version across the entire dependency tree.

Vulnerability at a Glance

cweCWE-1333 (Inefficient Regular Expression Complexity)
fixUpgrade path-to-regexp from 0.1.12 to 0.1.13 and pin the version with a package.json `overrides` entry
riskAn unauthenticated attacker can stall the Node.js event loop by sending a single crafted HTTP request, denying service to all legitimate users
languageJavaScript / Node.js
root causeAmbiguous regex patterns in path-to-regexp 0.1.12 allow exponential backtracking when matching malformed URL parameter strings
vulnerabilityRegular Expression Denial of Service (ReDoS)

How Denial of Service via Catastrophic Backtracking Happens in Node.js and How to Fix It

In the BakaMusic plugin subscription service (bakamusic-plugins), a routine dependency audit surfaced a medium-to-high severity vulnerability hiding inside a small but widely used routing helper: path-to-regexp. The Trivy scanner flagged rule CVE-2026-4867 against package-lock.json, pointing directly at version 0.1.12 of the package. A single HTTP request with a malformed URL parameter is all it would take to freeze the Node.js event loop and deny service to every user of the application.

This post walks through exactly what the vulnerability is, how an attacker would exploit it, and the two-file change that closes the door.


The Vulnerability Explained

What is Catastrophic Backtracking?

Regular expression engines work by trying every possible way to match a pattern against an input string. Most of the time this is fast. But certain patterns — particularly those with nested or ambiguous quantifiers — can cause the engine to explore an exponentially growing number of paths when the input doesn't match. This is called catastrophic backtracking, and it is the root cause of ReDoS (Regular Expression Denial of Service) attacks.

path-to-regexp converts URL route templates like /user/:id into regular expressions used to match incoming request paths. In version 0.1.12, the generated regex for certain parameter patterns contained ambiguous constructs. When a crafted string is fed to that regex — for example, a URL parameter containing a long sequence of repeated characters followed by a character that forces a mismatch — the regex engine enters an exponential backtracking loop.

The Vulnerable Dependency

The vulnerable entry in package-lock.json before the fix:

"node_modules/path-to-regexp": {
  "version": "0.1.12",
  "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
  "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
  "license": "MIT"
}

Note that the package was resolved from registry.npmmirror.com (a Chinese npm mirror), which is a secondary concern — the primary issue is the version number 0.1.12.

The Attack Scenario

Consider the bakamusic-plugins service, which exposes HTTP endpoints that match plugin subscription paths. Express (or a similar framework) uses path-to-regexp internally to compile route patterns. When a request arrives, the compiled regex is evaluated against the request URL.

An attacker sends a single HTTP GET request with a URL like:

GET /plugins/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!

The trailing ! is the mismatch character. The regex engine compiled from the route pattern by path-to-regexp 0.1.12 begins backtracking through all possible ways to match the long sequence of a characters, never succeeding, and consuming 100% of one CPU core in the process. Because Node.js runs on a single-threaded event loop, every other request queues up and waits. The server becomes unresponsive for the duration of the backtracking computation — which can be seconds or even minutes depending on input length.

No authentication is required. No special privileges are needed. One request, one stalled server.

Real-World Impact for BakaMusic

The bakamusic-plugins service is described as a plugin subscription service. If route matching is performed on user-supplied plugin identifiers or subscription paths, any unauthenticated user (or an automated scanner) could trigger this condition. The result is a complete denial of service for all subscribers trying to access the platform.


The Fix

The fix involves exactly two files: package-lock.json and package.json.

1. Upgrading the Resolved Version in package-lock.json

 "node_modules/path-to-regexp": {
-  "version": "0.1.12",
-  "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
-  "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
+  "version": "0.1.13",
+  "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz",
+  "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==",
   "license": "MIT"
 }

Two things changed here beyond the version bump:
- The registry URL switched from registry.npmmirror.com back to the official registry.npmjs.org, ensuring the package is sourced from the canonical registry.
- The integrity hash is updated to match the new 0.1.13 tarball, preventing any tampering or substitution.

Version 0.1.13 patches the regex patterns that caused catastrophic backtracking. The fix in the upstream library introduces input validation and rewrites the offending patterns to use possessive quantifiers or atomic groups (depending on the engine), ensuring linear-time matching regardless of input content.

2. Pinning the Version via package.json Overrides

This is the more important change from a long-term security perspective:

 "engines": {
   "node": ">=18"
+},
+"overrides": {
+  "path-to-regexp": "0.1.13"
 }

The overrides field (introduced in npm 8.3) forces all packages in the dependency tree — not just direct dependencies — to use path-to-regexp@0.1.13. This matters because path-to-regexp is typically a transitive dependency (pulled in by Express, for example). Without the override, running npm install after a lockfile deletion could silently reinstall 0.1.12 if a parent package's version range permits it.

Before the fix: The lockfile pinned 0.1.12, but nothing in package.json prevented a future npm install from resolving back to a vulnerable version.

After the fix: The overrides entry acts as a hard floor — npm will always resolve path-to-regexp to 0.1.13 or higher, regardless of what transitive parents request.


Key Takeaways

  • path-to-regexp@0.1.12 is vulnerable to ReDoS via malformed URL parameters — any Node.js application using this version (directly or transitively through Express) is at risk until upgraded to 0.1.13.
  • Transitive dependencies are just as dangerous as direct ones — the vulnerability lived in node_modules/path-to-regexp, not in any code the BakaMusic team wrote, but it was fully exploitable through their HTTP surface.
  • The overrides field in package.json is the correct way to force a safe version across the entire dependency tree, not just the lockfile entry.
  • Switching the registry from npmmirror.com to registry.npmjs.org in the same fix improves supply chain integrity by sourcing the package from the canonical registry.
  • A single unauthenticated HTTP request is enough to trigger this vulnerability — there is no need for an attacker to have an account or any prior knowledge of the application.

How Orbis AppSec Detected This

  • Source: Incoming HTTP request URL path — user-controlled URL parameters supplied to the route matching layer of the bakamusic-plugins Node.js server.
  • Sink: The path-to-regexp@0.1.12 regex compilation and matching call, invoked for every incoming request against registered route patterns in node_modules/path-to-regexp.
  • Missing control: No input length validation before route matching, and no version constraint preventing the vulnerable 0.1.12 release from being installed as a transitive dependency.
  • CWE: CWE-1333 — Inefficient Regular Expression Complexity.
  • Fix: Upgraded path-to-regexp from 0.1.12 to 0.1.13 in package-lock.json and added an overrides entry in package.json to pin the safe version across the full 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-4867 is a reminder that a single small dependency — just one file, no native code, no network calls — can become the weakest link in an application's availability posture. The path-to-regexp package is used by millions of Node.js projects, often invisibly as a transitive dependency of Express. Version 0.1.12 contained regex patterns that could be exploited with a single crafted HTTP request to freeze the event loop of the bakamusic-plugins service entirely.

The fix is surgical and safe: upgrade to 0.1.13, switch back to the canonical npm registry, and use package.json overrides to make the constraint durable across future installs. Two files changed, zero behavior change for valid inputs, and the attack surface is closed.

Keep your dependency tree shallow where possible, audit it continuously, and treat transitive vulnerabilities with the same urgency as direct ones.


Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #2

Related Articles

critical

deleteNestedProperty Prototype Pollution via Dot-Notation Path

The `deleteNestedProperty` function in propertyUtils.ts allowed attackers to manipulate JavaScript object prototypes by passing specially crafted dot-notation paths like `__proto__.polluted`. A fix now blocks dangerous keys before processing, preventing prototype pollution attacks that could affect all objects in the application.

high

How Denial of Service via Infinite Loop Happens in JavaScript Dependencies and How to Fix It

CVE-2026-67213 is a high-severity denial of service vulnerability in nanoid before version 5.1.6 that triggers an infinite loop during random ID generation when processing specially crafted input. We upgraded nanoid across the entire dependency tree to patch this flaw and prevent attackers from freezing application threads. This fix ensures that ID generation remains resilient even when handling adversarial input patterns.

high

How Sensitive Data Exposure happens in Zotero plugins and how to fix it

A high-severity data exposure vulnerability in `Zotero.ts` automatically transmitted complete document metadata—including private notes, attachment paths, and tags—to external LLM services without user consent. The fix replaces broad `item.toJSON()` serialization with explicit field selection, sending only essential bibliographic data.

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 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.