const MAX_RETURN_PATH = 2048; const SIGN_IN_PATHS = ["/login", "/signup", "/logout", "/forgot-password", "/reset-password"]; /** * A same-origin path to return to after signing in, or null. Only plain absolute paths are accepted, so a * `next` value can never send the user to another site (`//evil`, `/\evil`, `https:…`, control characters). */ export function safeReturnPath(raw: unknown): string | null { if (typeof raw !== "string" || raw.length === 0 || raw.length > MAX_RETURN_PATH) return null; if (!raw.startsWith("/") || raw.startsWith("//") || raw.startsWith("/\\")) return null; if (/[\u0000-\u001f\u007f\\]/.test(raw)) return null; const path = raw.split(/[?#]/, 1)[0] ?? ""; if (SIGN_IN_PATHS.some((prefix) => path === prefix || path.startsWith(`${prefix}/`))) return null; return raw; } export type ReturnSearch = { next?: string }; /** `validateSearch` for sign-in pages: keeps `next` only when it's a safe path. */ export function returnSearch(search: Record): ReturnSearch { const next = safeReturnPath(search.next); return next ? { next } : {}; }