{"version":3,"file":"private-ip.mjs","names":[],"sources":["../../../../../../../ai/src/security/private-ip.ts"],"sourcesContent":["import { isIP } from \"node:net\";\n\n/**\n * True when `ip` is a private, loopback, link-local, unique-local,\n * carrier-grade-NAT, unspecified, or otherwise non-public address — the\n * set an SSRF guard must refuse. Accepts IPv4 and IPv6 literals (including\n * IPv4-mapped IPv6 like `::ffff:169.254.169.254`). A non-IP string returns\n * `false` (the caller resolves hostnames via DNS first).\n *\n * The cloud-metadata endpoint `169.254.169.254` is covered by the IPv4\n * link-local range `169.254.0.0/16`.\n */\nexport function isPrivateOrReservedIp(ip: string): boolean {\n  const family = isIP(ip);\n  if (family === 4) return isPrivateIpv4(ip);\n  if (family === 6) return isPrivateIpv6(ip);\n  return false;\n}\n\n/** Parse a dotted-quad into four octets, or `null` if malformed. */\nfunction parseIpv4(ip: string): [number, number, number, number] | null {\n  const parts = ip.split(\".\");\n  if (parts.length !== 4) return null;\n\n  const octets = parts.map(part => Number(part));\n  if (octets.some(n => !Number.isInteger(n) || n < 0 || n > 255)) return null;\n\n  return octets as [number, number, number, number];\n}\n\nfunction isPrivateIpv4(ip: string): boolean {\n  const octets = parseIpv4(ip);\n  if (!octets) return true; // unparseable → refuse, fail closed\n\n  const [a, b] = octets;\n\n  return (\n    a === 0 || // 0.0.0.0/8 \"this network\"\n    a === 10 || // 10.0.0.0/8 private\n    a === 127 || // 127.0.0.0/8 loopback\n    (a === 100 && b >= 64 && b <= 127) || // 100.64.0.0/10 CGNAT\n    (a === 169 && b === 254) || // 169.254.0.0/16 link-local + metadata\n    (a === 172 && b >= 16 && b <= 31) || // 172.16.0.0/12 private\n    (a === 192 && b === 168) || // 192.168.0.0/16 private\n    (a === 192 && b === 0) || // 192.0.0.0/24 + 192.0.2.0/24 (IETF/test)\n    (a === 198 && (b === 18 || b === 19)) || // 198.18.0.0/15 benchmarking\n    (a === 198 && b === 51) || // 198.51.100.0/24 test-net-2\n    (a === 203 && b === 0) || // 203.0.113.0/24 test-net-3\n    a >= 224 // 224.0.0.0/4 multicast + 240.0.0.0/4 reserved + 255.255.255.255\n  );\n}\n\nfunction isPrivateIpv6(ip: string): boolean {\n  const normalized = ip.toLowerCase().split(\"%\")[0]; // drop zone id\n\n  // IPv4-mapped / -embedded (::ffff:a.b.c.d, ::a.b.c.d) — defer to the v4\n  // check on the trailing dotted-quad so an inward-mapped address is caught.\n  const v4 = normalized.match(/(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})$/);\n  if (v4) {\n    return isPrivateIpv4(v4[1]);\n  }\n\n  if (normalized === \"::1\" || normalized === \"::\") {\n    return true; // loopback / unspecified\n  }\n\n  // Expand only the leading group enough to classify the reserved blocks.\n  const firstGroup = normalized.split(\":\")[0];\n  const head = firstGroup === \"\" ? 0 : Number.parseInt(firstGroup, 16);\n\n  // fc00::/7 unique-local (fc.. / fd..)\n  if ((head & 0xfe00) === 0xfc00) return true;\n  // fe80::/10 link-local\n  if ((head & 0xffc0) === 0xfe80) return true;\n\n  return false;\n}\n"],"mappings":";;;;;;;;;;;;;AAYA,SAAgB,sBAAsB,IAAqB;CACzD,MAAM,SAAS,KAAK,EAAE;CACtB,IAAI,WAAW,GAAG,OAAO,cAAc,EAAE;CACzC,IAAI,WAAW,GAAG,OAAO,cAAc,EAAE;CACzC,OAAO;AACT;;AAGA,SAAS,UAAU,IAAqD;CACtE,MAAM,QAAQ,GAAG,MAAM,GAAG;CAC1B,IAAI,MAAM,WAAW,GAAG,OAAO;CAE/B,MAAM,SAAS,MAAM,KAAI,SAAQ,OAAO,IAAI,CAAC;CAC7C,IAAI,OAAO,MAAK,MAAK,CAAC,OAAO,UAAU,CAAC,KAAK,IAAI,KAAK,IAAI,GAAG,GAAG,OAAO;CAEvE,OAAO;AACT;AAEA,SAAS,cAAc,IAAqB;CAC1C,MAAM,SAAS,UAAU,EAAE;CAC3B,IAAI,CAAC,QAAQ,OAAO;CAEpB,MAAM,CAAC,GAAG,KAAK;CAEf,OACE,MAAM,KACN,MAAM,MACN,MAAM,OACL,MAAM,OAAO,KAAK,MAAM,KAAK,OAC7B,MAAM,OAAO,MAAM,OACnB,MAAM,OAAO,KAAK,MAAM,KAAK,MAC7B,MAAM,OAAO,MAAM,OACnB,MAAM,OAAO,MAAM,KACnB,MAAM,QAAQ,MAAM,MAAM,MAAM,OAChC,MAAM,OAAO,MAAM,MACnB,MAAM,OAAO,MAAM,KACpB,KAAK;AAET;AAEA,SAAS,cAAc,IAAqB;CAC1C,MAAM,aAAa,GAAG,YAAY,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;CAI/C,MAAM,KAAK,WAAW,MAAM,uCAAuC;CACnE,IAAI,IACF,OAAO,cAAc,GAAG,EAAE;CAG5B,IAAI,eAAe,SAAS,eAAe,MACzC,OAAO;CAIT,MAAM,aAAa,WAAW,MAAM,GAAG,CAAC,CAAC;CACzC,MAAM,OAAO,eAAe,KAAK,IAAI,OAAO,SAAS,YAAY,EAAE;CAGnE,KAAK,OAAO,WAAY,OAAQ,OAAO;CAEvC,KAAK,OAAO,WAAY,OAAQ,OAAO;CAEvC,OAAO;AACT"}