remix-run/react-router · critical
Invalid redirect location
Error message
Invalid redirect location
What it means
Before honoring a redirect target, normalizeRedirectLocation validates its protocol; hasInvalidProtocol rejects dangerous schemes (e.g. javascript:) and the function throws 'Invalid redirect location' (lib/router/router.ts:6920). This is a security guard: following or echoing a javascript:/data: Location would let untrusted input become script execution.
Solutions
- Whitelist redirect targets: only allow values starting with '/' (no '//') or absolute URLs on your own origin
- Return 400 for suspicious or scheme-carrying redirect inputs instead of passing them to redirect()
- If proxying upstream responses, validate/sanitize the Location header before returning it
- Add tests covering 'javascript:', 'data:', and '//' protocol-relative payloads
Example fix
// before
return redirect(request.url.searchParams.get('redirectTo') ?? '/')
// after
function safeRedirect(to: string | null) {
if (to && to.startsWith('/') && !to.startsWith('//')) return to
return '/'
}
return redirect(safeRedirect(request.url.searchParams.get('redirectTo'))) Defensive patterns
Strategy: validation
Validate before calling
function isSafeRedirectTarget(to: string | null | undefined): boolean {
if (!to) return false;
if (to.startsWith('/') && !to.startsWith('//')) return true;
try {
const url = new URL(to, 'https://example.invalid');
return url.protocol === 'https:' || url.protocol === 'http:';
} catch {
return false;
}
}
if (!isSafeRedirectTarget(redirectTo)) throw new Response('Bad redirect', { status: 400 }); Type guard
type SafeRedirect = `/${string}`;
function asSafeRedirect(to: string): SafeRedirect | null {
return to.startsWith('/') && !to.startsWith('//') && !to.includes(':') ? (to as SafeRedirect) : null;
} Try / catch
try { return redirect(target) } catch (e) { if (e instanceof Error && e.message === 'Invalid redirect location') throw new Response('Bad redirect target', { status: 400 }) throw e } Prevention
- Treat all redirect targets from user input as untrusted; whitelist origins and relative paths
- Reject values containing ':' before the first '/' or starting with '//' or a scheme
- Add security tests with 'javascript:' and 'data:' payloads
- Sanitize Location headers received from proxied upstreams
When it happens
Trigger: A loader/action returns redirect(input) where input came from a query param, DB field, or upstream service and equals something like 'javascript:alert(1)'; a proxied backend returns a Location header with a non-http(s) scheme; protocol-relative or malformed URLs that parse to an invalid protocol.
Common situations: Open redirect targets taken from ?redirectTo= or ?next= stored and replayed; user profile fields used as post-login redirects; untrusted upstream APIs behind a proxy whose Location headers are forwarded.
Related errors
- Invalid redirect location
- Invalid redirect location
- Expected a route.id in react-router processRoutes() function
- Invalid route exports found when prerendering with…
- `origin` header is not a valid URL. Aborting the action.
AI-assisted analysis of remix-run/react-router@7aea711dd1 (2026-08-18).
Data as JSON: /api/errors/8257a93da2dbf452.
Report an issue: GitHub.
Appendix: source
Thrown at packages/react-router/lib/router/router.ts:6899
}
}
function normalizeRedirectLocation(
location: string,
currentUrl: URL,
basename: string,
historyInstance: History,
): string {
if (isAbsoluteUrl(location)) {
// Strip off the protocol+origin for same-origin + same-basename absolute redirects
let normalizedLocation = location;
let url = PROTOCOL_RELATIVE_URL_REGEX.test(normalizedLocation)
? new URL(
normalizeProtocolRelativeUrl(normalizedLocation, currentUrl.protocol),
)
: new URL(normalizedLocation);
if (hasInvalidProtocol(url.toString())) {
throw new Error("Invalid redirect location");
}
let isSameBasename = stripBasename(url.pathname, basename) != null;
if (url.origin === currentUrl.origin && isSameBasename) {
return removeDoubleSlashes(url.pathname) + url.search + url.hash;
}
}
try {
let url = historyInstance.createURL(location);
if (hasInvalidProtocol(url.toString())) {
throw new Error("Invalid redirect location");
}
} catch {}
return location;
}
// Utility method for creating the Request instances for loaders/actions duringView on GitHub (pinned to 7aea711dd1)