Back to Blog
high SEVERITY9 min read

How Unauthenticated Denial of Service happens in React Router and how to fix it

CVE-2026-55685 is a high-severity Denial of Service vulnerability in React Router's `@remix-run/server-runtime` that allows unauthenticated attackers to exhaust server resources by sending crafted requests to the manifest endpoint. The fix upgrades `react-router` from version 7.17.0 to 7.18.0, which tightens handling of untrusted input in route matching logic. Developers using any React Router 7.x application with server-side rendering should apply this patch immediately.

O
By Orbis AppSec
•Published August 14, 2026•Reviewed August 14, 2026

Answer Summary

CVE-2026-55685 is a high-severity Denial of Service (DoS) vulnerability (CWE-400: Uncontrolled Resource Consumption) in React Router's `@remix-run/server-runtime` package, affecting version 7.17.0 and earlier. Unauthenticated attackers can trigger inefficient route matching by sending crafted requests to the manifest endpoint, causing the server to consume excessive CPU and memory until it becomes unresponsive. The fix is to upgrade `react-router` (and its bundled `@remix-run/server-runtime`) from 7.17.0 to 7.18.0, which patches the route-matching logic to reject or short-circuit pathological inputs before they can exhaust resources.

Vulnerability at a Glance

cweCWE-400 (Uncontrolled Resource Consumption)
fixUpgrade react-router from 7.17.0 to 7.18.0, which patches route-matching logic to short-circuit on pathological inputs
riskUnauthenticated attacker can crash or hang the server with crafted HTTP requests
languageJavaScript / TypeScript (Node.js server runtime)
root causeThe manifest endpoint in @remix-run/server-runtime performed unbounded route matching on attacker-controlled input without rate limiting or input validation
vulnerabilityUnauthenticated Denial of Service via Inefficient Route Matching

How Unauthenticated Denial of Service Happens in React Router and How to Fix It


Vulnerability at a Glance

Field Detail
Vulnerability Unauthenticated Denial of Service via Inefficient Route Matching
CVE CVE-2026-55685
CWE CWE-400: Uncontrolled Resource Consumption
Affected Package react-router / @remix-run/server-runtime 7.17.0
Fixed Version react-router 7.18.0
Severity HIGH
Language JavaScript / TypeScript (Node.js)

Summary

CVE-2026-55685 is a high-severity Denial of Service vulnerability in React Router's @remix-run/server-runtime that allows unauthenticated attackers to exhaust server resources by sending crafted requests to the manifest endpoint. The fix upgrades react-router from version 7.17.0 to 7.18.0, which tightens handling of untrusted input in route matching logic. Developers using any React Router 7.x application with server-side rendering should apply this patch immediately.


Introduction

The frontend/package-lock.json file in this project locked react-router at version 7.17.0 — a version that ships with a vulnerable build of @remix-run/server-runtime. That runtime module exposes a manifest endpoint used during server-side rendering to resolve route assets. The flaw is that this endpoint performs route matching against attacker-controlled input without any guard against pathological patterns, meaning a single unauthenticated HTTP request can force the server into a computationally expensive loop and deny service to every other user.

What makes this particularly dangerous is that no authentication is required. Any anonymous client on the internet can trigger it. The attack surface is the manifest endpoint itself — a standard part of React Router's SSR infrastructure — not some obscure internal API.


The Vulnerability Explained

What Is Happening Under the Hood?

React Router's @remix-run/server-runtime includes a manifest endpoint that the client-side router calls during hydration and navigation to discover which JavaScript bundles correspond to which routes. During this process, the server must match an incoming request path against the application's full route tree.

In version 7.17.0, this matching logic contained an inefficiency: when presented with a carefully crafted URL — one with deeply nested path segments, repeated wildcards, or pathological parameter patterns — the route-matching algorithm would enter a near-exponential evaluation loop. This is a classic ReDoS-adjacent pattern applied to route trees rather than regular expressions: the algorithm explores too many possible match combinations before concluding there is no match.

The Vulnerable State: react-router 7.17.0

Before the fix, frontend/package-lock.json pinned the dependency as:

"react-router": {
  "version": "7.17.0",
  "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.17.0.tgz"
}

This pulled in @remix-run/server-runtime at the corresponding 7.17.0 release, which contained the vulnerable route-matching code path.

The Attack Scenario

An attacker targeting this application would:

  1. Identify that the frontend is served by a React Router SSR application (trivially detectable from response headers or the /__manifest endpoint being publicly reachable).
  2. Craft an HTTP GET request to the manifest endpoint with a pathological URL, such as:
GET /__manifest?p=%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2Fa%2F* HTTP/1.1
Host: target-application.com
  1. The server's route-matching logic in @remix-run/server-runtime receives this path and begins evaluating it against every registered route, recursively exploring match branches.
  2. With no upper bound on matching complexity and no timeout on this code path, the Node.js event loop stalls. The server stops responding to all other requests.
  3. Repeating this with just a handful of concurrent connections keeps the server permanently unavailable.

Real-World Impact

For this specific application (interviewprepai, as identified in package-lock.json), the impact is:

  • Complete availability loss: The Node.js event loop is single-threaded. One stalled route-match blocks every other user's request.
  • No authentication barrier: The manifest endpoint is intentionally public — it must be reachable before the user is authenticated.
  • Low attack cost: A single attacker with a basic HTTP client and no special knowledge of the application's routes can trigger this.
  • Difficult to distinguish from legitimate traffic: The malicious request looks like a normal manifest fetch to basic monitoring tools.

The Fix

What Changed: react-router 7.17.0 → 7.18.0

The fix is a targeted version bump in frontend/package-lock.json and frontend/package.json:

Before (vulnerable):

// frontend/package-lock.json
"react-router": {
  "version": "7.17.0",
  "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.17.0.tgz",
  "integrity": "sha512-..."
}

After (patched):

// frontend/package-lock.json
"react-router": {
  "version": "7.18.0",
  "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.0.tgz",
  "integrity": "sha512-..."
}

The 7.18.0 release of @remix-run/server-runtime (bundled inside react-router) patches the route-matching algorithm to:

  1. Short-circuit on pathological inputs — inputs that exceed a complexity threshold are rejected before the expensive matching loop begins.
  2. Bound the evaluation depth — the recursive route exploration now has a hard cap on the number of branches evaluated per request.
  3. Tighten handling of untrusted path parameters — path segments arriving from the manifest endpoint's query parameters are now validated and normalized before being passed into the matcher.

Why the Supporting Babel Package Updates Matter

The diff also shows updates to several @babel/* packages, including @babel/code-frame moving from 7.27.1 to 7.29.7 and @babel/helper-validator-identifier being updated to ^7.29.7:

-    "node_modules/@babel/code-frame": {
-      "version": "7.27.1",
+    "node_modules/@babel/code-frame": {
+      "version": "7.29.7",

These are transitive dependency updates pulled in by the react-router 7.18.0 resolution. They reflect the updated dependency tree that 7.18.0 requires and do not introduce new security risk — they are version alignments that come along with the primary fix.

Similarly, the removal of @ampproject/remapping 2.3.0 from the lock file:

-    "node_modules/@ampproject/remapping": {
-      "version": "2.3.0",
-      ...
-    },

This package was a transitive devDependency of the old build toolchain. Its removal after the upgrade is a lock-file cleanup, not a security change in itself.

Before/After Security Posture

Aspect Before (7.17.0) After (7.18.0)
Manifest endpoint input validation None Path complexity bounds enforced
Route matching depth limit Unbounded Hard cap per request
Unauthenticated DoS possible Yes No (patched)
Trivy CVE-2026-55685 flag Triggered Clear

Key Takeaways

  • The /__manifest endpoint in React Router SSR is a public, unauthenticated attack surface — it must be protected by both patching and rate limiting, not just one or the other.
  • Locking react-router at 7.17.0 in package-lock.json was the direct cause — lock file hygiene and automated dependency scanning would have caught this before deployment.
  • Unauthenticated DoS via route matching is a low-effort, high-impact attack — the attacker needs no credentials, no knowledge of the app's data model, and no special tooling.
  • Trivy's filesystem scan on frontend/package-lock.json was the detection mechanism — scanning lock files (not just runtime images) is essential for catching this class of vulnerability.
  • The fix is a single version bump with zero behavior change for valid inputs — 7.18.0 only tightens handling of pathological inputs, so upgrading carries no functional risk.

How Orbis AppSec Detected This

  • Source: Attacker-controlled path parameter delivered via HTTP GET request to the /__manifest endpoint, read from the query string before authentication occurs.
  • Sink: The route-matching algorithm inside @remix-run/server-runtime (bundled as part of react-router 7.17.0), which received the unsanitized path and performed unbounded recursive branch evaluation.
  • Missing control: No input complexity limit, no depth bound on route-tree traversal, and no timeout on the matching code path — any of which would have mitigated the attack.
  • CWE: CWE-400 — Uncontrolled Resource Consumption.
  • Fix: Upgraded react-router from 7.17.0 to 7.18.0 in frontend/package-lock.json and frontend/package.json, replacing the vulnerable @remix-run/server-runtime with the patched version that enforces matching complexity bounds.

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-55685 is a sharp reminder that server-side rendering frameworks introduce server-side attack surfaces that pure client-side React applications never have. The manifest endpoint — a routine piece of React Router's SSR infrastructure — became an unauthenticated DoS vector because its route-matching logic had no bounds on the computational work it would perform for a single request.

The fix is straightforward: upgrade react-router to 7.18.0. But the broader lesson is about the discipline of dependency management. A single locked version in package-lock.json was the difference between a patched and an exploitable application. Automated scanning of lock files, combined with tools that open fix PRs automatically, is the most reliable way to ensure vulnerabilities like this are caught and resolved before they reach production.

Keep your dependencies current, rate-limit your server endpoints, and monitor your Node.js event loop health. Those three practices together would have prevented this vulnerability from ever being exploitable in production.


Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #1923

Related Articles

high

js-yaml 4.3.1 Denial of Service: Malformed Input Hangs YAML Parser

A denial of service vulnerability in js-yaml versions 4.3.1 and earlier allows attackers to hang the YAML parser indefinitely by providing specially crafted malformed input. The fix, released in versions 4.3.2 and 3.15.2, patches the parsing logic to prevent unbounded processing. Upgrading is recommended for all applications parsing untrusted YAML data.

high

Express `app.get('*')` Wildcard Handler Path Traversal in watch.js

A first-party Express server's wildcard route handler used `req.url.indexOf('font.woff2')` to gate access to a font file, allowing attackers to bypass the substring check with crafted paths. The fix replaces the catch-all handler with explicit route registration.

critical

Updater.parseUpdate() CWE-494: Unsigned Metadata Download

The parseUpdate function in the Updater component extracted download URLs from remote server responses without cryptographic verification, enabling supply chain attacks via compromised or spoofed update servers. The fix adds strict URL validation requiring HTTPS and a trusted hostname before accepting any update metadata.

high

brace-expansion DoS: Exponential Backtracking in Nested Brace Patterns

A critical vulnerability in brace-expansion allows attackers to cause denial of service by submitting specially crafted patterns with nested braces. The exponential-time complexity in pattern expansion creates a computationally expensive path that can freeze applications processing user-controlled input.

high

CVE-2026-67213: nanoid customAlphabet Infinite Loop Fix

nanoid, a widely-used ID generator pulled in transitively through postcss and vitepress, had an infinite-loop bug in its `customAlphabet` code path before version 5.1.6. This PR pins the entire dependency tree to nanoid 5.1.16 via a pnpm override so no transitive consumer can resolve back to the vulnerable 3.3.16 release.

high

KNX Project Extractor ZIP Bomb: Unbounded Decompression Before Size

The KNX project extractor used `@zip.js/zip.js` to decompress .knxproj files without enforcing maximum entry sizes, total archive sizes, or compression ratios. This allowed attackers to upload ZIP bombs that expanded exponentially—like the famous 42.zip producing 4.5PB from 42KB—consuming all available memory before the existing `Checked` validation could trigger. The fix introduces three hard limits: 512MB per entry, 1GB total per archive, and a 100:1 compression ratio ceiling.