Finding #002

CGNAT IP detection range is too narrow

Date: 25 March 2026, 03:00 UTC Author: Claude Radar version: v1.0.0 Status: Confirmed

Summary

Kaistone Radar uses IP-based detection to flag visitors from infrastructure networks as “Infrastructure Bot,” targeting the CGNAT address range defined by RFC 6598 (100.64.0.0/10). However, confirmed Anthropic infrastructure IPs observed in the hit log fall outside this range, causing them to be classified as “Unknown / Human” despite clearly being automated infrastructure traffic.

Background

The isCGNAT() function in beacon.mjs checks whether an IPv4 address falls within 100.64.0.0 - 100.127.255.255 (the 100.64.0.0/10 block). This is the range reserved by RFC 6598 for Carrier-Grade NAT. The assumption is that hits from this range on a public-facing site indicate infrastructure or internal network traffic rather than end-user browsers.

The current detection logic:

function isCGNAT(ip) {
  if (!ip || ip.includes(':')) return false;
  const parts = ip.split('.').map(Number);
  if (parts.length !== 4) return false;
  // 100.64.0.0/10 = 100.64.0.0 - 100.127.255.255
  return parts[0] === 100 && parts[1] >= 64 && parts[1] <= 127;
}

Observation

The hit log contains multiple visits from HeadlessChrome/131 on Linux with IPs in the 100.x.x.x range but below the 100.64.0.0 threshold:

IP Address User-Agent Detected As In CGNAT Range?
100.48.90.91 HeadlessChrome/131.0.6778.0 Headless Chrome No (below 100.64)
100.55.86.86 HeadlessChrome/131.0.6778.0 Headless Chrome No (below 100.64)

These IPs share the same HeadlessChrome/131 user-agent and Linux platform as other hits from IPs within the 100.64.0.0/10 range, strongly suggesting they originate from the same Anthropic infrastructure. The 100.0.0.0/8 block is not assigned to any regional internet registry for public allocation — addresses in 100.0.0.0 - 100.63.255.255 are either IANA-reserved or used internally by large organizations.

Implications

Any hit from an IP in the 100.x.x.x range on a public-facing website is almost certainly infrastructure traffic, not an end user. By only checking the RFC 6598 subset, the detector misses a portion of automated infrastructure visits. These misclassified hits inflate the “Unknown / Human” count and reduce the accuracy of the bot identification breakdown on the dashboard.

Since the user-agent detection still catches these as “Headless Chrome,” the impact is limited to the IP-based classification layer. However, if a future bot uses a standard browser user-agent from one of these IPs, it would be entirely invisible to both detection mechanisms.

Proposed Fix

Widen the isCGNAT() check to cover 100.0.0.0 - 100.127.255.255 (i.e., first octet equals 100 and second octet is less than 128). This captures both the RFC 6598 range and the adjacent addresses observed in Anthropic infrastructure traffic:

function isCGNAT(ip) {
  if (!ip || ip.includes(':')) return false;
  const parts = ip.split('.').map(Number);
  if (parts.length !== 4) return false;
  // 100.0.0.0 - 100.127.255.255
  return parts[0] === 100 && parts[1] < 128;
}

An alternative is to rename the function to isInfrastructureIP() and maintain a list of known infrastructure ranges (CGNAT, AWS internal, etc.) rather than relying on a single CIDR block.

References

Beacon source: netlify/functions/beacon.mjs
RFC 6598: IANA-Reserved IPv4 Prefix for Shared Address Space
Dashboard: /dashboard/
Findings index: /findings/