// Shared URL policy. Used both at request entry (server.js) and per-request // inside the renderer (renderer.js) so redirects to private IPs are blocked. const PRIVATE_HOSTNAMES = new Set([ "localhost", "127.0.0.1", "0.0.0.0", "[::1]", "::1", ]); /** * Returns true if `urlStr` is a public http(s) URL we're willing to fetch. * Rejects: non-http(s) schemes, localhost, IPv6 literals, RFC1918 IPv4, * link-local, and anything that fails to parse. */ export function isAllowedUrl(urlStr) { let parsed; try { parsed = new URL(urlStr); } catch { return false; } if (!["http:", "https:"].includes(parsed.protocol)) return false; const host = parsed.hostname; if (PRIVATE_HOSTNAMES.has(host)) return false; if (host.startsWith("[")) return false; // any IPv6 literal — overly cautious by design if (host === "::1") return false; // RFC1918 + link-local IPv4 ranges const parts = host.split("."); if (parts[0] === "10") return false; if (parts[0] === "172" && +parts[1] >= 16 && +parts[1] <= 31) return false; if (parts[0] === "192" && parts[1] === "168") return false; if (parts[0] === "169" && parts[1] === "254") return false; return true; }