{"record":{"id":"542c8015a439ba52","repo":"sveltejs/kit","slug":"cannot-redirect-to-external-url-json-stringify-l","errorCode":null,"errorMessage":"Cannot redirect to external URL ${JSON.stringify(location)}. To redirect to an external URL, pass `{ external: true }` or an allowlist of permitted origins as the third argument to `redirect` (prod: 'Cannot redirect to external URL unless explicitly allowed')","messagePattern":"Cannot redirect to external URL (.+?)\\. To redirect to an external URL, pass `(.+?)` or an allowlist of permitted origins as the third argument to `redirect` \\(prod: 'Cannot redirect to external URL unless explicitly allowed'\\)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/kit/src/exports/url.js","lineNumber":48,"sourceCode":"function is_javascript_location(location) {\n\ttry {\n\t\treturn javascript_protocols.has(new URL(location, REDIRECT_BASE).protocol);\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * @param {string} location\n * @param {{ external?: boolean | string[] }} [options]\n */\nexport function validate_redirect_location(location, options) {\n\tif (!is_external_location(location)) return;\n\n\tconst external = options?.external;\n\n\tif (!external) {\n\t\tthrow new Error(\n\t\t\tDEV\n\t\t\t\t? `Cannot redirect to external URL ${JSON.stringify(location)}. ` +\n\t\t\t\t\t\t'To redirect to an external URL, pass `{ external: true }` or an allowlist of permitted origins as the third argument to `redirect`'\n\t\t\t\t: 'Cannot redirect to external URL unless explicitly allowed'\n\t\t);\n\t}\n\n\tif (external === true) {\n\t\tif (is_javascript_location(location)) {\n\t\t\tthrow new Error(\n\t\t\t\tDEV\n\t\t\t\t\t? `Cannot redirect to ${JSON.stringify(location)} with \\`{ external: true }\\`. ` +\n\t\t\t\t\t\t\t'The `javascript:` and `data:` protocols must be explicitly listed in the `external` allowlist'\n\t\t\t\t\t: 'Cannot redirect to external URL unless explicitly allowed'\n\t\t\t);\n\t\t}\n\n\t\treturn;","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/sveltejs/kit/blob/03f1687fe612ce3d2d9131139b5b188d9cf90c64/packages/kit/src/exports/url.js#L30-L66","documentation":"`redirect()` refuses external (absolute cross-origin) URLs by default. To redirect off-site you must explicitly opt in with `{ external: true }` or pass an allowlist of permitted origins as the third argument, protecting against open-redirect vulnerabilities. In production the message is intentionally vague to avoid leaking validation details.","triggerScenarios":"Calling `redirect(302, 'https://other-site.com/path')` without options; redirecting to an absolute URL built from query params like `?next=https://evil.com` without `{ external: true }` or an origins allowlist.","commonSituations":"Post-login `?redirectTo=` flows pointing at absolute URLs; integrating OAuth return URLs; environment-based redirects where a base URL is absolute (e.g. `https://app.example.com/dashboard`) even in same-origin deployments.","solutions":["If the target is truly external, call `redirect(status, location, { external: true })`.","Better: pass an allowlist of permitted origins as the third argument, e.g. `redirect(302, url, { external: ['https://partner.com'] })`.","If the target should be internal, strip the origin: use `new URL(location).pathname + new URL(location).search` before redirecting.","Never derive the redirect target directly from untrusted input without allowlisting."],"exampleFix":"// before\nredirect(302, event.url.searchParams.get('next'));\n// after\nconst next = event.url.searchParams.get('next') ?? '/';\nredirect(302, next, { external: ['https://trusted.example.com'] });","handlingStrategy":"validation","validationCode":"const allowedExternalOrigins = ['https://trusted.example.com'];\nfunction canRedirect(loc, opts) {\n  const u = new URL(loc, 'http://internal');\n  const isExternal = u.origin !== 'http://internal';\n  if (!isExternal) return true;\n  return Array.isArray(opts?.external)\n    ? opts.external.includes(u.origin)\n    : opts?.external === true;\n}\nif (!canRedirect(target, opts)) target = '/';","typeGuard":"const isExternalUrl = (s) => {\n  try { return new URL(s, 'http://internal').origin !== 'http://internal'; } catch { return false; }\n};","tryCatchPattern":"try {\n  redirect(302, target);\n} catch (e) {\n  if (String(e.message).includes('external URL')) {\n    redirect(302, '/'); // or re-redirect with allowlist\n  } else throw e;\n}","preventionTips":["Normalize internal targets to pathname+search before redirecting.","Maintain an explicit origin allowlist for genuine external redirects.","Never pass untrusted ?next= parameters through as absolute URLs.","Consider Redirect(302, '/') as a fallback for blocked targets."],"tags":["redirect","open-redirect","security","validation","sveltekit"],"backgroundTag":"open-redirect-blocked","analyzedSha":"03f1687fe612ce3d2d9131139b5b188d9cf90c64","analyzedAt":"2026-09-02T02:01:50.504Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-09T06:17:21.866Z"}