/** * Truncates an IP address per DPA: * IPv4 → /24 ("192.168.1.42" → "192.168.1.0") * IPv6 → /48 ("2001:0db8:1234:5678:..." → "2001:0db8:1234::") * * Throws on malformed input rather than silently returning the raw value — * compliance regimes prefer hard failures over partial scrubbing. */ export function truncateIp(raw: string): string { if (raw.includes(":")) { // IPv6: keep first 3 hextets (48 bits) const parts = raw.toLowerCase().split(":").filter((p) => p !== ""); if (parts.length < 3) { throw new Error(`truncateIp: malformed IPv6 address "${raw}"`); } return `${parts[0]}:${parts[1]}:${parts[2]}::`; } // IPv4: keep first 3 octets (24 bits) const parts = raw.split("."); if ( parts.length !== 4 || parts.some((p) => p === "" || isNaN(Number(p)) || !/^\d+$/.test(p)) ) { throw new Error(`truncateIp: malformed IPv4 address "${raw}"`); } return `${parts[0]}.${parts[1]}.${parts[2]}.0`; }