groundy
security

React Router CVE-2025-31137: Vercel's Edge Fix Is Not the Patch

Vercel's edge mitigation for CVE-2025-31137 covers only traffic on its network. Self-hosted, preview, and non-Vercel Remix deploys still need the adapter patch.

8 min · · · 6 sources ↓

Vercel declared platform customers “unaffected” by CVE-2025-31137 and, in the same changelog entry, still told them to upgrade. Both statements hold, and together they describe what the protection is and is not. The Express-adapter Host-header flaw disclosed on April 1, 2025 under GHSA-4q56-crqp-v477 still runs in any Remix or React Router deploy carrying an unpatched adapter, wherever that traffic terminates. Vercel’s mitigation covers requests on its own network. It is not the patch.

What CVE-2025-31137 actually is

CVE-2025-31137 is a high-severity URL-spoofing vulnerability in the Express adapter for Remix and React Router, disclosed on April 1, 2025 in the maintainer’s advisory GHSA-4q56-crqp-v477 and catalogued by NVD under CWE-444, the class for inconsistent interpretation of HTTP requests and request smuggling.

The flaw sits in how the adapter builds the server-side Request object from incoming headers. A Host or X-Forwarded-Host header is meant to carry a hostname, optionally with a port. An attacker who can control that header can place a URL pathname in the port slot, and the adapter then constructs a Request whose request.url is attacker-controlled, per SentinelOne’s mechanism writeup. That synthesized URL is what downstream code uses for routing, validation, and access control, so anything gated on the URL (route matching, allow/deny checks, origin assumptions) can be bypassed.

NVD scopes the practical impact to routing bypass, cache poisoning, and denial of service, achievable with a single unauthenticated network request. Exploitation needs no credentials and no application logic beyond a vulnerable adapter willing to trust the Host family of headers, which is precisely what the Express adapter did. That low bar (one request, no auth) is what puts the flaw in the class of issues worth scanning the open internet for rather than dismissing as theoretical. The researchers credited in the maintainer advisory are Rachid Allam (zhero) and Yasser Allam (inzo).

Which Remix and React Router versions are affected

The bug is confined to the Express adapter, and the fix landed in two coordinated releases. The GitHub Advisory lists the affected ranges:

PackageVulnerable rangePatched in
@react-router/express>= 7.0.0, < 7.4.17.4.1
@remix-run/express>= 2.11.1, < 2.16.32.16.3

That scope is the first triage filter. Deployments on the Node adapter, the Cloudflare adapter, Vite’s dev server, or any other non-Express entry point are not named in the advisory. If you do not depend on @remix-run/express or @react-router/express, this CVE is not your CVE.

The teams still exposed are the ones who wired the Express adapter into a long-running Node process behind their own reverse proxy. That is the configuration in which an attacker-controlled X-Forwarded-Host can travel from the edge to the adapter untouched, which is the whole premise of the exploit.

What Vercel’s protection actually does

Vercel attributes its “unaffected” status to two specific properties of its platform, not to a generic WAF signature matching a payload pattern. The changelog states them directly: Vercel includes query parameters in its cache key, which closes the cache-poisoning vector that runs through the loader’s _data query parameter; and an end user cannot deliver an X-Forwarded-Host header to a Function hosted on Vercel, because the header a Function sees is the one Vercel sets itself. The attacker-controlled value never reaches the adapter’s URL construction. On the cache point, Remix loaders answer client data fetches on requests distinguished by a _data query parameter; keying the cache on that parameter means two requests that differ only in _data do not collide, which is the collision a cache-poisoning attacker needs.

The detail that vendor reassurance tends to omit: the @vercel/remix adapter consumes X-Forwarded-Host the same way the Express adapter does, so the vulnerable code path is present on Vercel too. What protects Vercel customers is the network and cache architecture in front of that adapter, not a hardened adapter. The protection is structural and scoped to Vercel’s ingress, not a property of the Remix code your build ships.

That is why Vercel still recommends updating to Remix 2.16.3 and React Router 7.4.1 in the same entry. “Unaffected” is a claim about observed exploitability on Vercel’s platform given its architecture; “still update” is a claim about the dependency. A team that reads only the first half inherits the bug intact.

What stays exposed once traffic leaves Vercel’s network

Every ingress that does not cross Vercel’s network is out of scope for Vercel’s mitigation. The dividing line is whether Vercel terminates the request, not where your repository lives. The exposed surfaces include:

  • Self-hosted Remix on a VM, container, or bare-metal box behind your own proxy
  • Deployments on other hosting providers, where cache-keying and X-Forwarded-Host behavior depend on that provider’s architecture
  • Internal, staging, or preview environments you operate yourself, which frequently run the same unpatched adapter as production
  • Any path where traffic is terminated at an origin you control before it reaches the adapter

Note the boundary precisely. A Vercel preview deploy terminates on Vercel’s network and is covered; a staging box you run on your own infrastructure is not. If your reverse proxy is the one setting or forwarding X-Forwarded-Host, and your cache layer keys on path rather than path-plus-query, the exploit path is open no matter where the rest of your traffic goes. The protection travels with the traffic, not with the codebase, which is why a platform-wide “unaffected” notice cannot stand in for a dependency check.

Stopgaps when you cannot upgrade immediately

If a same-day dependency bump is blocked, the viable stopgaps all attack the same assumption the vulnerability depends on: that the adapter can trust a client-supplied Host or X-Forwarded-Host. SentinelOne’s entry documents the standard set, and each one denies that trust:

  • A reverse-proxy rule that rejects malformed Host or X-Forwarded-Host values before they reach the adapter
  • Express middleware that overwrites req.headers.host (and the forwarded host) with a value the application controls
  • A hostname allowlist enforced at the load balancer
  • Upstream proxies configured to strip client-supplied X-Forwarded-Host and set their own trusted value

The last measure does the most work. The entire vulnerability rests on the adapter reading an attacker-controlled X-Forwarded-Host; if your proxy strips or replaces it before it reaches Node, the port-injection path collapses because the adapter no longer has a hostile value to parse. None of these patch the adapter. They deny the vulnerable input.

Verifying the patch in your dependency tree

The remediation is a version bump: @react-router/express to 7.4.1, or @remix-run/express to 2.16.3, with no configuration change required. The advisory treats the version move as the fix, and the stopgaps above exist only because not every team can move on the same day.

Verify before you declare done. Run npm ls against both package names to see what resolves in your tree, and npm audit to confirm the advisory no longer fires. Transitive resolution bites here. An application that never directly imports the Express adapter can still pull it in through a starter template, a framework wrapper, or a lockfile that has not been refreshed since the 2.x line, and npm audit is the reliable way to surface that indirect exposure.

The gap is not hypothetical. OpenDataHub’s odh-dashboard pull request #4337 (tracking RHOAIENG-27386), still open in 2026, exists specifically to upgrade react-router and react-router-dom to clear CVE-2025-31137, confirmed through npm audit. A vulnerability disclosed and patched in April 2025 is still generating upgrade work in enterprise codebases more than a year later. That is the realistic remediation timeline for an unglamorous Express-adapter CVE, and it is the reason an edge mitigation is welcome but insufficient: it buys time, it does not retire the work.

Frequently Asked Questions

Does CVE-2025-31137 affect Remix apps on the Cloudflare or Vite adapter, or only Express?

The advisory names only @remix-run/express and @react-router/express. The Node, Cloudflare, and Vite adapters are out of scope because they do not build request.url from a client-supplied X-Forwarded-Host through the same code path. The practical trap is hybrid deploys: a codebase mid-migration that carries both the Cloudflare and Express adapters is vulnerable on every request routed through the Express entry point, even if most traffic takes the Cloudflare path.

Is Vercel’s protection a WAF rule that matches the exploit payload?

No, and that distinction is what makes it hold. A WAF signature matches a byte pattern in a request, which breaks against encoding variants and needs re-tuning per new payload. Vercel’s mitigation removes the two preconditions the exploit needs: it does not forward end-user X-Forwarded-Host to Functions, and it keys its cache on query parameters including the loader’s _data value. That is architectural, so it covers future header-injection variants in the same class without a rule update.

Where does the Express middleware workaround that overwrites req.headers.host break down?

It fails on any request that bypasses the middleware chain. WebSocket upgrades are handled at the HTTP server level before Express middleware runs, and so are health checks registered before the overwrite. The fix is also order-sensitive: any code that reads request.url before the overwrite executes sees the poisoned value. Teams relying on this stopgap should place the overwrite first in the pipeline and audit sibling endpoints served outside Express.

Should a team patch @remix-run/express 2.16.3 or migrate to @react-router/express 7.4.1?

Both close the CVE, but they sit on different maintenance trajectories. React Router 7 is the merged, active line that absorbed Remix, so the @react-router/express 7.x adapter is the one receiving ongoing work. @remix-run/express 2.16.3 keeps the legacy 2.x line patched for teams not ready to migrate, but the 7.x adapter is the durable home for the Express entry point. A team with a free upgrade window should move to 7.4.1 rather than pin to 2.16.3.

Can an attacker exploit this with only the Host header, or is X-Forwarded-Host required?

Either header alone is enough. The advisory and NVD entry describe the payload as living in the port slot of whichever Host-family header the adapter reads, and the Express adapter honors both. On a deploy where the edge strips X-Forwarded-Host but passes the raw Host through unmodified, the attacker falls back to Host, which is why the documented stopgaps cover both headers rather than the forwarded variant alone.

sources · 6 cited

  1. NVD - CVE-2025-31137 nvd.nist.gov primary accessed 2026-06-23
  2. SentinelOne: CVE-2025-31137 React Router auth bypass vulnerability sentinelone.com analysis accessed 2026-06-23