{"record":{"id":"8257a93da2dbf452","repo":"remix-run/react-router","slug":"invalid-redirect-location","errorCode":null,"errorMessage":"Invalid redirect location","messagePattern":"Invalid redirect location","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"packages/react-router/lib/router/router.ts","lineNumber":6899,"sourceCode":"  }\n}\n\nfunction normalizeRedirectLocation(\n  location: string,\n  currentUrl: URL,\n  basename: string,\n  historyInstance: History,\n): string {\n  if (isAbsoluteUrl(location)) {\n    // Strip off the protocol+origin for same-origin + same-basename absolute redirects\n    let normalizedLocation = location;\n    let url = PROTOCOL_RELATIVE_URL_REGEX.test(normalizedLocation)\n      ? new URL(\n          normalizeProtocolRelativeUrl(normalizedLocation, currentUrl.protocol),\n        )\n      : new URL(normalizedLocation);\n    if (hasInvalidProtocol(url.toString())) {\n      throw new Error(\"Invalid redirect location\");\n    }\n    let isSameBasename = stripBasename(url.pathname, basename) != null;\n    if (url.origin === currentUrl.origin && isSameBasename) {\n      return removeDoubleSlashes(url.pathname) + url.search + url.hash;\n    }\n  }\n\n  try {\n    let url = historyInstance.createURL(location);\n    if (hasInvalidProtocol(url.toString())) {\n      throw new Error(\"Invalid redirect location\");\n    }\n  } catch {}\n\n  return location;\n}\n\n// Utility method for creating the Request instances for loaders/actions during","sourceCodeStart":6881,"sourceCodeEnd":6917,"githubUrl":"https://github.com/remix-run/react-router/blob/7aea711dd1ae2bc5a076d13ff17291829690fa74/packages/react-router/lib/router/router.ts#L6881-L6917","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nreturn redirect(request.url.searchParams.get('redirectTo') ?? '/')\n// after\nfunction safeRedirect(to: string | null) {\n  if (to && to.startsWith('/') && !to.startsWith('//')) return to\n  return '/'\n}\nreturn redirect(safeRedirect(request.url.searchParams.get('redirectTo')))","handlingStrategy":"validation","validationCode":"function isSafeRedirectTarget(to: string | null | undefined): boolean {\n  if (!to) return false;\n  if (to.startsWith('/') && !to.startsWith('//')) return true;\n  try {\n    const url = new URL(to, 'https://example.invalid');\n    return url.protocol === 'https:' || url.protocol === 'http:';\n  } catch {\n    return false;\n  }\n}\nif (!isSafeRedirectTarget(redirectTo)) throw new Response('Bad redirect', { status: 400 });","typeGuard":"type SafeRedirect = `/${string}`;\nfunction asSafeRedirect(to: string): SafeRedirect | null {\n  return to.startsWith('/') && !to.startsWith('//') && !to.includes(':') ? (to as SafeRedirect) : null;\n}","tryCatchPattern":"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 }","preventionTips":["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"],"tags":["redirect","security","validation","xss"],"backgroundTag":"unsafe-redirect","analyzedSha":"7aea711dd1ae2bc5a076d13ff17291829690fa74","analyzedAt":"2026-08-18T18:04:14.938Z","contentChangedAt":"2026-08-18T18:04:14.938Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}