import { getDomain } from "tldts"; /** * Extract eTLD+1 domain from a URL string. * Returns null for invalid URLs. * * `allowPrivateDomains: true` is the origin-binding control, not a preference: * under tldts' default every host beneath a shared private suffix (`github.io`, * `vercel.app`, `s3.amazonaws.com` and thousands more) collapses to that * suffix, so `octocat.github.io` and `evil.github.io` compared EQUAL and a * saved login was offered to the attacker's tenant. The sibling passkey matcher * (passkey-utils.ts) already passes the option (PB-26-901). * * Keys derived from this value — `manualSummonDomains`, the * `peach_state11_shown:` flag — were previously the collapsed suffix. * Those entries simply stop matching rather than matching more broadly, which * is the fail-safe direction, so they are left in place instead of migrating * (the stored records carry no URL to re-derive from). */ export function extractDomain(url: string): string | null { try { const normalized = url.startsWith("http") ? url : "https://" + url; const parsed = getDomain(normalized, { allowPrivateDomains: true }); return parsed ?? null; } catch { return null; } } /** * Get displayable domain from a URL string. */ export function getDisplayDomain(url: string): string { try { const normalized = url.startsWith("http") ? url : "https://" + url; const { hostname } = new URL(normalized); return hostname; } catch { return url; } } /** * Preserve the browser security boundary users need to inspect before a * credential or trust decision: scheme, complete hostname, and port. */ export function getSecurityOriginDisplay(url: string): string { try { return new URL(url).origin; } catch { return url; } } /** * LV-2: true when the URL's hostname contains non-ASCII characters — a * potential homograph (Cyrillic "аpple.com" etc.) — OR a punycode (xn--) * label, which is the URL parser's normalized form of the same risk. * Matching/keying is NOT affected (tldts keeps Unicode labels distinct, * preventing cross-matching); this only drives a non-blocking UI warning so a * user who saved or imported such a URL can see it isn't the ASCII site. * Accepts bare hostnames as well as full URLs. */ export function detectHomograph(url: string): boolean { try { const hostname = new URL(url.includes("://") ? url : `https://${url}`).hostname; return ( // biome-ignore lint/suspicious/noControlCharactersInRegex: intentional — detects non-ASCII hostnames /[^\x00-\x7F]/.test(hostname) || hostname.split(".").some((l) => l.startsWith("xn--")) ); } catch { return false; } }