How to Fix an Open Redirect Vulnerability
An open redirect vulnerability means your /redirect endpoint will forward users to any URL an attacker supplies - making your domain the launch pad for phishing. The fix is a two-part check: the target must start with / AND must not start with //.
What makes a redirect endpoint vulnerable
A typical post-login redirect looks like this:
app.get('/redirect', (req, res) => {
const target = req.query.url;
res.redirect(target); // no validation
});
An attacker sends /redirect?url=https://evil.com in a phishing email.
The link domain is yours, so spam filters and users trust it - but the
browser lands on the attacker's site.
The two-part validation rule
A safe relative path starts with / (e.g. /dashboard) and is not
protocol-relative (e.g. //evil.com). Protocol-relative URLs start with //
and are treated as external by browsers, so startsWith('/') alone is not enough.
app.get('/redirect', (req, res) => {
const target = req.query.url;
// Must start with "/" and must NOT start with "//"
if (
typeof target === 'string' &&
target.startsWith('/') &&
!target.startsWith('//')
) {
return res.redirect(302, target);
}
// Reject - fall back to a safe default
res.redirect(302, '/');
});
The same pattern applies to vanilla Node.js http without Express:
function isSafeRedirect(target) {
return (
typeof target === 'string' &&
target.startsWith('/') &&
!target.startsWith('//')
);
}
// inside your request handler:
const target = url.searchParams.get('url');
const location = isSafeRedirect(target) ? target : '/';
res.writeHead(302, { Location: location });
res.end();
Why blocklisting external URLs does not work
A common but flawed approach is checking whether the URL contains your own domain. Substring matching is easy to bypass:
https://yourcompany.com.evil.comcontainsyourcompany.com- URL encoding and unicode normalization add more bypasses
Allowlisting the shape (relative path only) is the reliable approach. If you genuinely need cross-domain redirects, use a hardcoded allowlist of exact destination URLs validated through a proper URL parser - never string contains or startsWith on the hostname.
Test your fix
# Should redirect to /dashboard (302)
curl -i "http://localhost:3000/redirect?url=/dashboard"
# Should NOT redirect to evil.com - expect redirect to / or a 400
curl -i "http://localhost:3000/redirect?url=https://evil.com"
# Protocol-relative bypass - should also be rejected
curl -i "http://localhost:3000/redirect?url=//evil.com"
Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.
Try it in a workspace →What you'll practice
- Spotting an open redirect in a Node.js /redirect endpoint
- Applying the two-part relative-path check (starts with / but not //)
- Writing curl tests to verify both the safe and the attack-path cases
FAQ
What is an open redirect vulnerability?
An open redirect is when a redirect endpoint accepts any URL from user input without validation. Attackers craft links on your domain (e.g. yoursite.com/redirect?url=https://evil.com) that look legitimate but send users to a malicious site.
Why is checking startsWith('/') not enough to fix an open redirect?
Protocol-relative URLs like //evil.com start with / but browsers treat them as external URLs pointing to evil.com. You must also reject anything starting with //. The safe rule is: starts with / AND does not start with //.
How do I fix an open redirect in Node.js or Express?
Validate the redirect target before using it: only allow strings that start with / and do not start with //. Anything else - an absolute URL, a protocol-relative URL, or a non-string - should fall back to a safe default like /.
Keep learning
Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.
Open the workspace →