| // Open-redirect protection: only allow http(s) URLs whose host is explicitly allowed. | |
| // Provider "requires_action" URLs are validated before navigation. | |
| export function isSafeExternalUrl(url: string, allowedHosts: readonly string[]): boolean { | |
| let u: URL | |
| try { | |
| u = new URL(url) | |
| } catch { | |
| return false | |
| } | |
| if (u.protocol !== 'https:' && u.protocol !== 'http:') return false | |
| return allowedHosts.some( | |
| (h) => u.hostname === h || u.hostname.endsWith(`.${h}`), | |
| ) | |
| } | |
| /** Relative in-app paths are always safe (no host); reject protocol-relative //evil. */ | |
| export function isSafeInternalPath(path: string): boolean { | |
| return path.startsWith('/') && !path.startsWith('//') | |
| } | |