// post-login targets are attacker-controlled (login links, stored pending // state), so resolve against an opaque origin and only accept paths that // stay on it. also covers network-path forms like "/\evil.com" that the url // parser turns into a foreign host const SAFE_BASE = "https://return-to.invalid"; export const internalUrl = (value: unknown): URL | null => { if (typeof value !== "string" || value.trim() === "") return null; let resolved: URL; try { resolved = new URL(value, SAFE_BASE); } catch { return null; } return resolved.origin === SAFE_BASE ? resolved : null; }; export const safeReturnTo = (value: unknown): string => { const resolved = internalUrl(value); return resolved ? `${resolved.pathname}${resolved.search}${resolved.hash}` : "/"; };