If you're building web apps in React using Remix or React Router, there's pressing security news you need to know. A recent vulnerability—CVE-2025-31137—was found that could let attackers spoof request URLs, opening a big can of worms for security. Let’s unpack what happened, who’s at risk, and how you fix it.

What Actually is CVE-2025-31137?

This bug affects users of Remix 2 and React Router 7 who use the popular Express adapter for server-side rendering or routing.

Basically, attackers can trick the routing logic by putting parts of a URL path (stuff after your site’s domain, such as /evil/path) into the *port* section of a host header. Remix and React Router's Express handler didn’t suspect a thing, so it’d reconstruct a request with any "pathname" hidden in the port, meaning URLs became unreliable and potentially dangerous.

In plain English:
> Attackers can smuggle fake URLs into your request handler by crafting malicious headers, making your app see the wrong route.

Only with Express (other adapters may not be vulnerable)

If you’re running an older version *and* using Express, this concerns you.

Your app is running

import express from "express";
import { createRequestHandler } from "@remix-run/express"; // or "react-router/express"

const app = express();

app.all(
  "*",
  createRequestHandler({ /* ...options... */ })
);

app.listen(300);

Suppose an attacker crafts this HTTP request

GET / HTTP/1.1
Host: victim.com:80/foo/bar

or:

X-Forwarded-Host: victim.com:4444/fake/path

Normally, the Host header provides just the domain and port (victim.com:80). But here—maliciously—it includes /foo/bar in the port spot. Because of the adapter bug, the Express handler reconstructs the request from these headers and accepts /foo/bar as the real path!

Let’s look at a (simplified) example of the bug, before the patch

import express from "express";
import { createRequestHandler } from "@remix-run/express";

const app = express();

app.all("*", (req, res, next) => {
  // Simulating the bug: build URL with trust in 'Host' header
  const protocol = req.protocol;
  const host = req.get("host"); // reads Host header verbatim

  // 🐞 THE BUG: Using full 'host' from user input
  const url = new URL(${protocol}://${host}${req.originalUrl});

  res.send(I think this is your pathname: ${url.pathname});
});

app.listen(300);

With a request

Host: example.com:300/hel/lo

Your endpoint sees url.pathname as "/hel/lo", not what the browser actually requested!

You can simulate the attack using curl

curl -H "Host: localhost:300/fake/path" http://localhost:300/real

Expected: /real
With Vulnerability: /fake/path

React Router v7.4.1

New versions now sanitize and strictly validate the Host and X-Forwarded-Host headers. They parse out the port correctly and refuse to treat anything after : as a path. This blocks the spoof.

Upgrade:

- Remix: npm install @remix-run/express@^2.16.3

Consider disabling trust proxy if you don’t actually need it.

- Audit logs for suspicious host/path headers in recent requests.

Further Reading & References

- Remix 2.16.3 Security Release
- React Router 7.4.1 Release Notes
- GitHub Advisory (look for CVE-2025-31137)
- OWASP HTTP Host header attacks

Conclusion

If your web app uses Remix 2 or React Router 7 with Express, this vulnerability means requests could be misrouted—or worse. Update your dependencies, and keep an eye on your request headers. Security happens in the details!

Don’t wait—upgrade now and review your request handling. Happy (and safe) coding!

Timeline

Published on: 04/01/2025 19:15:45 UTC
Last modified on: 04/01/2025 20:26:01 UTC