/** * Guard CC API base URLs against classic SSRF (metadata / RFC1918). * Allows official CarrierCentral hosts and localhost for local mock/dev. * Hosts listed in `CC_API_BASE` env (or matching that hostname) also allowed. */ function isPrivateOrSpecialHost(hostname: string): boolean { const h = hostname.toLowerCase().replace(/^\[|\]$/g, ""); if (h === "localhost" || h === "127.0.0.1" || h === "::1") return false; if (h === "0.0.0.0" || h === "169.254.169.254" || h === "metadata.google.internal") { return true; } // IPv4 private / link-local / loopback const m = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(h); if (m) { const a = Number(m[1]); const b = Number(m[2]); if (a === 10) return true; if (a === 127) return true; if (a === 0) return true; if (a === 169 && b === 254) return true; if (a === 192 && b === 168) return true; if (a === 172 && b >= 16 && b <= 31) return true; } // IPv6 ULA / loopback / link-local if (h.startsWith("fc") || h.startsWith("fd") || h.startsWith("fe80")) return true; return false; } function hostAllowedByDefault(hostname: string): boolean { const h = hostname.toLowerCase(); if (h === "localhost" || h === "127.0.0.1" || h === "::1") return true; if (h === "carriercentral.vip" || h.endsWith(".carriercentral.vip")) return true; return false; } function hostMatchesEnvBase(hostname: string): boolean { const raw = process.env.CC_API_BASE?.trim(); if (!raw) return false; try { return new URL(raw).hostname.toLowerCase() === hostname.toLowerCase(); } catch { return false; } } /** @returns normalized URL string (no trailing slash required) */ export function assertSafeCcApiBase(urlStr: string): string { let u: URL; try { u = new URL(urlStr.trim()); } catch { throw new Error("CC_API_BASE_INVALID"); } if (u.protocol !== "https:" && u.protocol !== "http:") { throw new Error("CC_API_BASE_PROTOCOL"); } const host = u.hostname; if (!host) throw new Error("CC_API_BASE_INVALID"); // Production: https only except localhost const isLocal = host === "localhost" || host === "127.0.0.1" || host === "::1"; if (process.env.NODE_ENV === "production" && u.protocol !== "https:" && !isLocal) { throw new Error("CC_API_BASE_HTTPS_REQUIRED"); } if (isPrivateOrSpecialHost(host) && !isLocal) { throw new Error("CC_API_BASE_PRIVATE_HOST"); } if (!hostAllowedByDefault(host) && !hostMatchesEnvBase(host)) { throw new Error( "CC_API_BASE_HOST_NOT_ALLOWED: 仅允许 *.carriercentral.vip、localhost,或与 CC_API_BASE 同主机", ); } return u.toString().replace(/\/$/, ""); }