/** * Guard against open-redirect abuse. * * Given a user-supplied redirect target (e.g. `?next=...`), return either * that target (if it passes every check) or a safe fallback. Never trust * the input value on its own — always redirect to `result.url`. * * const result = safeRedirect(req.query.next, { * allowedHosts: ['app.example.com', '*.example.com'], * defaultTo: '/dashboard', * }) * if (!result.safe) log.warn({ reason: result.reason, next: req.query.next }) * res.redirect(result.url) * * Rejected inputs (the returned `reason` codes): * - `empty` — missing / non-string * - `illegal-chars` — whitespace, control chars, backslashes * - `protocol-relative` — `//evil.com/foo` (browsers resolve to host) * - `relative-not-allowed`— path input while `allowRelative: false` * - `malformed` — URL constructor threw * - `scheme` — scheme not in allowlist, or hard-banned * - `userinfo` — `https://evil@safe.com` phishing trick * - `host` — hostname not in `allowedHosts` * * @param {unknown} input The user-supplied redirect target. * @param {SafeRedirectOptions} [options] * @returns {SafeRedirectResult} */ declare function safeRedirect(input: unknown, options?: SafeRedirectOptions): SafeRedirectResult; /** * @typedef {object} ExtractReturnUrlOptions * @property {string[]} [queryParams=['next','return_to','returnTo','redirect','redirect_uri']] * Query param names to check, in priority order. * @property {string | string[]} [headerName] * Header name(s) to fall back to (e.g. `'x-return-to'`). Case-insensitive. * @property {string} [cookieName] * Cookie name to check as a last resort. Requires cookies to be * pre-parsed onto `req.cookies` (adapter middleware does this). */ /** * Pick the user-supplied "come back to" URL from wherever the client * stashed it — query string, header, or cookie. Returns the first * non-empty candidate. Does NOT validate — pipe the result into * `safeRedirect` before actually redirecting: * * const raw = extractReturnUrl(req, { * queryParams: ['next', 'return_to'], * headerName: 'x-return-to', * }) * const { safe, url } = safeRedirect(raw, { allowedHosts: [...] }) * res.redirect(url) * * @param {{ * query?: Record, * headers?: Record, * cookies?: Record, * }} req * @param {ExtractReturnUrlOptions} [options] * @returns {string | undefined} */ declare function extractReturnUrl(req: { query?: Record; headers?: Record; cookies?: Record; }, options?: ExtractReturnUrlOptions): string | undefined; /** * Compare two URLs by origin (scheme + host + port). Uses WHATWG URL * parsing so `https://example.com` and `https://example.com:443/foo` * compare equal. Returns `false` on any parse failure — never throws. * * Complements `checkOrigin`, which reads `Origin` / `Referer` headers off * a request. `isSameOrigin` is the primitive both callers land on when * they need direct URL-to-URL comparison (OAuth callback validation, * Referer-based flow checks, allowlist-of-one matching). * * @param {string | URL | undefined | null} a * @param {string | URL | undefined | null} b * @returns {boolean} */ declare function isSameOrigin(a: string | URL | undefined | null, b: string | URL | undefined | null): boolean; type ExtractReturnUrlOptions = { /** * Query param names to check, in priority order. */ queryParams?: string[] | undefined; /** * Header name(s) to fall back to (e.g. `'x-return-to'`). Case-insensitive. */ headerName?: string | string[] | undefined; /** * Cookie name to check as a last resort. Requires cookies to be * pre-parsed onto `req.cookies` (adapter middleware does this). */ cookieName?: string | undefined; }; type SafeRedirectOptions = { /** * Where to send the user when the input fails validation. Must itself be * a safe target — validated on option parse. */ defaultTo?: string | undefined; /** * Hostnames (case-insensitive) that are trusted external targets. Use a * leading `*.` for subdomain wildcards: `*.example.com` matches * `app.example.com` and `sub.app.example.com` but NOT `example.com` * itself. Absolute URLs whose hostname doesn't match any entry are * rejected. Omit to disallow all absolute URLs. */ allowedHosts?: string | string[] | undefined; /** * Schemes permitted on absolute URLs. `javascript:` / `data:` / `vbscript:` * are ALWAYS rejected regardless of this option — a bug in your allowlist * shouldn't turn into XSS. */ allowedSchemes?: string[] | undefined; /** * Whether same-origin paths (`/foo`, `/foo?bar`) are accepted. Turn off * for flows where you want an explicit allowlist even for internal URLs. */ allowRelative?: boolean | undefined; }; type SafeRedirectResult = { /** * True if the input was accepted; false when * the returned `url` is the fallback. */ safe: boolean; /** * The URL you should redirect to. */ url: string; /** * When unsafe, a short code explaining why * (`'empty' | 'illegal-chars' | 'protocol-relative' * | 'malformed' | 'scheme' | 'userinfo' | 'host' * | 'relative-not-allowed'`). Useful for logging. */ reason?: string | undefined; }; export { extractReturnUrl, isSameOrigin, safeRedirect }; export type { ExtractReturnUrlOptions, SafeRedirectOptions, SafeRedirectResult };