UNPKG

@nanggo/social-preview

Version:

Generate beautiful social media preview images from any URL

142 lines (141 loc) 5.59 kB
"use strict"; /** * IP Address Validation Utilities * Centralized SSRF protection for private and reserved IP address ranges */ Object.defineProperty(exports, "__esModule", { value: true }); exports.isPrivateOrReservedIP = isPrivateOrReservedIP; /** * Check if an IP address (IPv4 or IPv6) is in a private or reserved range * This prevents SSRF attacks by blocking access to internal network resources */ function isPrivateOrReservedIP(ip) { // IPv6 address detection if (ip.includes(':')) { return isPrivateOrReservedIPv6(ip); } // IPv4 address validation return isPrivateOrReservedIPv4(ip); } /** * Check if an IPv4 address is in a private or reserved range */ function isPrivateOrReservedIPv4(ip) { const octets = ip.split('.').map(Number); if (octets.length !== 4 || octets.some(isNaN) || octets.some((octet) => octet < 0 || octet > 255)) { return true; // Invalid IP format, treat as blocked } const [a, b] = octets; // IPv4 private and reserved ranges if (a === 0) return true; // 0.0.0.0/8 (reserved) if (a === 10) return true; // 10.0.0.0/8 if (a === 172 && b >= 16 && b <= 31) return true; // 172.16.0.0/12 if (a === 192 && b === 168) return true; // 192.168.0.0/16 // Carrier-Grade NAT (RFC 6598) if (a === 100 && b >= 64 && b <= 127) return true; // 100.64.0.0/10 // Loopback if (a === 127) return true; // 127.0.0.0/8 // Link-local if (a === 169 && b === 254) return true; // 169.254.0.0/16 // Multicast and reserved if (a >= 224) return true; // 224.0.0.0/3 return false; } /** * Check if an IPv6 address is in a private or reserved range */ function isPrivateOrReservedIPv6(ip) { try { // Normalize IPv6 address - remove brackets if present const normalizedIP = ip.replace(/^\[|\]$/g, '').toLowerCase(); // Exact match addresses (must be precise) const exactMatches = [ '::', // Unspecified address (0:0:0:0:0:0:0:0) '::1', // Loopback address (exact match only) ]; // Check exact matches first for (const exact of exactMatches) { if (normalizedIP === exact) { return true; } } // IPv6 private and reserved prefixes const privatePrefixes = [ 'fe80:', // Link-local 'fec0:', // Site-local (deprecated but still reserved) 'ff', // Multicast (ff00::/8) 'fc', // Unique local addresses (fc00::/7) 'fd', // Unique local addresses (fd00::/8) '2001:db8:', // Documentation prefix '2002:', // 6to4 addresses ]; // Check against known private/reserved prefixes for (const prefix of privatePrefixes) { if (normalizedIP.startsWith(prefix)) { return true; } } // Comprehensive check for IPv4-mapped IPv6 addresses if (normalizedIP.startsWith('::ffff:')) { const ipv4Part = normalizedIP.replace('::ffff:', ''); // Handle dot notation IPv4 (e.g., ::ffff:192.168.1.1) if (ipv4Part.includes('.')) { return isPrivateOrReservedIPv4(ipv4Part); } // Handle hex notation IPv4 (e.g., ::ffff:c0a8:101 for 192.168.1.1) if (ipv4Part.length === 8 && /^[0-9a-f]+$/.test(ipv4Part)) { const hexPart1 = ipv4Part.slice(0, 4); const hexPart2 = ipv4Part.slice(4, 8); const octet1 = parseInt(hexPart1.slice(0, 2), 16); const octet2 = parseInt(hexPart1.slice(2, 4), 16); const octet3 = parseInt(hexPart2.slice(0, 2), 16); const octet4 = parseInt(hexPart2.slice(2, 4), 16); const reconstructedIPv4 = `${octet1}.${octet2}.${octet3}.${octet4}`; return isPrivateOrReservedIPv4(reconstructedIPv4); } // Handle colon-separated hex notation (e.g., ::ffff:c0a8:101) if (ipv4Part.includes(':')) { const hexParts = ipv4Part.split(':'); if (hexParts.length === 2) { try { const part1 = parseInt(hexParts[0], 16); const part2 = parseInt(hexParts[1], 16); const octet1 = (part1 >> 8) & 0xff; const octet2 = part1 & 0xff; const octet3 = (part2 >> 8) & 0xff; const octet4 = part2 & 0xff; const reconstructedIPv4 = `${octet1}.${octet2}.${octet3}.${octet4}`; return isPrivateOrReservedIPv4(reconstructedIPv4); } catch { // If conversion fails, treat as blocked for security return true; } } } // If we can't parse the IPv4 part, treat as blocked for security return true; } // Also check for general IPv4-mapped patterns that don't start with ::ffff: // Some systems use different mappings if (normalizedIP.includes('::') && normalizedIP.match(/[0-9a-f]*\.[0-9a-f]*\.[0-9a-f]*\.[0-9a-f]*/)) { return true; // Block any suspicious IPv4-like patterns in IPv6 } return false; } catch { // If parsing fails, treat as blocked for security return true; } }