Safeguard
Security Guides•

How do you prevent open redirects with res.redirect in Express?

Never pass user supplied URLs straight to res.redirect. Parse the URL, compare the host to an allow list, and prefer fixed route names. Steps and a test.

Safeguard Security Team
4 min read

Parse the incoming URL, compare its host against an allow list, and refuse anything else. Better still, accept a short key that maps to a known destination instead of a URL. Never pass req.query values straight into res.redirect or res.location, because that turns your domain into a phishing launchpad.

Definition

An open redirect is when an application accepts a URL as input, often in a query string like ?url=, and uses it to set the Location header with a 3xx status. The Express security guide describes this as potentially dangerous user input, and says an application must validate that it supports redirecting to the incoming URL, to avoid sending users to malicious links such as phishing sites, among other risks.

It looks minor, but a link on a trusted domain that bounces to an attacker's page is convincing. Login flows that return users to a page after sign in are the usual place for it.

The Express guide's check

The security guide shows this approach: parse the URL with the URL constructor, compare the host, and reject on mismatch or parse error.

app.use((req, res) => {
  try {
    if (new URL(req.query.url).host !== 'example.com') {
      return res.status(400).end(`Unsupported redirect to host: ${req.query.url}`);
    }
  } catch (e) {
    return res.status(400).end(`Invalid url: ${req.query.url}`);
  }
  res.redirect(req.query.url);
});

Treat this as a starting point. Echoing the rejected URL back into the response, as the example does, can create its own reflected content problem if the response type allows markup, so return a fixed message in your own code.

Better patterns, ranked by safety

PatternSafetyNotes
Map a key to a fixed destinationHighestClient sends a name like dashboard, server picks the URL
Relative paths only, validatedHighReject anything starting with two slashes or containing a scheme
Allow list of exact hostsMedium to highCompare parsed host, not a substring
Block list of bad hostsLowAlways incomplete

Substring checks fail in predictable ways, for example a check that the URL contains your domain also passes for a hostile domain that merely includes it.

Steps

  1. Find every use of res.redirect and res.location. Search for them in your codebase.
  2. For each, ask where the destination comes from. If any part is user controlled, fix it.
  3. Prefer the key to destination map. Otherwise use parsed host comparison against exact entries.
  4. Restrict the scheme to https, and to http only for local development.
  5. Add tests with hostile inputs: another host, a scheme relative URL, an encoded value and a lookalike domain.
  6. Check recent advisories for your Express version, since redirect handling itself has had fixes. See CVE-2024-29041 open redirect.

Behind a proxy

If you build absolute URLs from req.hostname while trust proxy is on, the value comes from X-Forwarded-Host, which a client or proxy can set. Do not use it to build redirect targets unless the proxy overwrites it. See Express trust proxy.

Safeguard's DAST web scanning can probe parameters like these in a running app, and its dependency scanning flags advisories in the Express version you use. Try the free scan at gold.safeguard.sh/scan.

Quick check

Is checking that the URL starts with my domain enough?

No. Parse the URL and compare the host exactly. Prefix and substring checks are easy to bypass with lookalike hosts.

Can I just redirect to a relative path?

It is safer, but validate it. Paths beginning with two slashes can be read as another host by browsers.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.