Open Redirect — Unvalidated Redirect Target
Fix open redirects by validating redirect targets against an allowlist, allowing only relative paths, and avoiding reflecting the request parameter.
Symptoms
- Visiting /login?next=https://evil.example.com redirects off-site
- A security scanner reports an open redirect finding
- The same parameter is used as a return URL after login
Possible causes
- The redirect target comes from a request parameter without validation
- A check allows any URL starting with the host string but not the actual origin
- Protocol-relative or encoded URLs bypass a naive allowlist
Diagnosis steps
Reproduce with an external target
curl -i "https://app.example.com/login?next=https://evil.example.com" | grep -i locationInterpretation
A Location header pointing off-origin proves the redirect is unvalidated.
Locate redirect sinks
grep -rn "redirect(\|res.redirect\|Location" src/Interpretation
Every place a request value controls the redirect target is a candidate to guard.
Root cause
When the redirect target is taken from user input and used directly, an attacker crafts a link that appears to be on your domain but redirects elsewhere, enabling phishing and token theft.
Fix
Allow only same-origin relative paths (reject anything with a scheme or host), or validate the target against a strict allowlist of known destinations before redirecting.
Risk note
String prefix checks like startsWith("https://[email protected]") are unsafe; parse the URL and compare the origin exactly.
Verify the fix
Resend the external target and confirm it is rejected or replaced with a safe default; verify a legitimate relative path still redirects.
Symptoms
The error “Open Redirect — Unvalidated Redirect Target” points at a specific failure mode. This guide documents the symptoms you can observe, the commands that isolate the cause, the root cause, the fix, and the checks that prove the fix works.
Observed symptoms include:
- Visiting /login?next=https://evil.example.com redirects off-site
- A security scanner reports an open redirect finding
- The same parameter is used as a return URL after login
Reproduce and Diagnose
Reproduce the failure in isolation before changing any code. Work through the checks below in order; each one narrows the search space.
Step 1: Reproduce with an external target
curl -i "https://app.example.com/login?next=https://evil.example.com" | grep -i location
A Location header pointing off-origin proves the redirect is unvalidated.
Step 2: Locate redirect sinks
grep -rn "redirect(\|res.redirect\|Location" src/
Every place a request value controls the redirect target is a candidate to guard.
Likely contributors, in order of frequency:
- The redirect target comes from a request parameter without validation
- A check allows any URL starting with the host string but not the actual origin
- Protocol-relative or encoded URLs bypass a naive allowlist
Root Cause
When the redirect target is taken from user input and used directly, an attacker crafts a link that appears to be on your domain but redirects elsewhere, enabling phishing and token theft.
The Fix
Allow only same-origin relative paths (reject anything with a scheme or host), or validate the target against a strict allowlist of known destinations before redirecting.
Watch out: String prefix checks like startsWith(“https://[email protected]”) are unsafe; parse the URL and compare the origin exactly.
function safeRedirectTarget(next, fallback = '/') {
if (!next) return fallback;
try {
const url = new URL(next, 'https://app.example.com');
// allow only same-origin relative paths
if (url.origin !== 'https://app.example.com') return fallback;
return url.pathname + url.search + url.hash;
} catch {
return fallback;
}
}
Verify and Prevent
Resend the external target and confirm it is rejected or replaced with a safe default; verify a legitimate relative path still redirects.
Once verified, add a regression test that reproduces the original symptom so the failure cannot silently return.
This class of failure is usually environmental rather than pure logic, so capture the exact command, the environment variables, and the working directory in your bug report. Reproduce it in the smallest possible script, then widen the scope only once the minimal case is understood. A short written checklist for this failure mode saves hours the next time it appears.