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.
Prevention & Best Practices
1. Audit Transitive Dependencies Regularly
The vulnerability was in a transitive dependency — path-to-regexp was not listed directly in package.json before this fix. Use npm audit, Trivy, or Snyk in your CI pipeline to catch these:
npm audit --audit-level=moderate
trivy fs --scanners vuln .
2. Use overrides (npm) or resolutions (Yarn) Proactively
When a transitive dependency has a known vulnerability and the parent hasn't released a patch yet, pin the safe version yourself:
// package.json
"overrides": {
"path-to-regexp": ">=0.1.13"
}
3. Validate and Limit URL Parameter Length
Even with the patched library, defense in depth is valuable. Reject or truncate URL parameters that exceed a reasonable length before they reach route matching logic:
// Express middleware example
app.use((req, res, next) => {
if (req.path.length > 512) {
return res.status(400).json({ error: 'Path too long' });
}
next();
});
4. Audit Custom Regex Patterns
If your application defines its own regular expressions that match user input, audit them with tools like safe-regex or vuln-regex-detector:
npx safe-regex '/^(a+)+$/' # Returns: false (unsafe)
5. Relevant Standards
- OWASP: Input Validation Cheat Sheet
- CWE-1333: Inefficient Regular Expression Complexity
- OWASP Top 10 2021: A06 — Vulnerable and Outdated Components
Key Takeaways
path-to-regexp@0.1.12is vulnerable to ReDoS via malformed URL parameters — any Node.js application using this version (directly or transitively through Express) is at risk until upgraded to0.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
overridesfield inpackage.jsonis the correct way to force a safe version across the entire dependency tree, not just the lockfile entry. - Switching the registry from
npmmirror.comtoregistry.npmjs.orgin 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-pluginsNode.js server. - Sink: The
path-to-regexp@0.1.12regex compilation and matching call, invoked for every incoming request against registered route patterns innode_modules/path-to-regexp. - Missing control: No input length validation before route matching, and no version constraint preventing the vulnerable
0.1.12release from being installed as a transitive dependency. - CWE: CWE-1333 — Inefficient Regular Expression Complexity.
- Fix: Upgraded
path-to-regexpfrom0.1.12to0.1.13inpackage-lock.jsonand added anoverridesentry inpackage.jsonto 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.