import { describe, expect, test } from 'bun:test'; import { parseIpv4, subnetContains } from '@celilo/capabilities'; import { classifyIp, isPublicLeakSafe, isSegmentedZoneIp } from './zone-classifier'; describe('parseIpv4', () => { test('parses dotted-quad to unsigned 32-bit', () => { expect(parseIpv4('0.0.0.0')).toBe(0); expect(parseIpv4('255.255.255.255')).toBe(0xffffffff); // High first octet must not go negative (sign-bit overflow guard). A pure // arithmetic fixture, deliberately NOT an address from the sim plan: it used // to be the sim's internal natIp, so the guard would have quietly // evaporated the moment that zone was renumbered into 10/8 (#539). expect(parseIpv4('200.100.50.253')).toBe(((200 << 24) | (100 << 16) | (50 << 8) | 253) >>> 0); expect(parseIpv4('200.100.50.253')).toBeGreaterThan(0); }); test('rejects malformed / out-of-range', () => { expect(parseIpv4('not.an.ip')).toBeNull(); expect(parseIpv4('999.1.1.1')).toBeNull(); expect(parseIpv4('10.0.0')).toBeNull(); }); }); describe('subnetContains (shared, @celilo/capabilities)', () => { test('membership within /24', () => { expect(subnetContains('10.226.10.0/24', '10.226.10.5')).toBe(true); expect(subnetContains('10.226.10.0/24', '10.226.10.255')).toBe(true); expect(subnetContains('10.226.10.0/24', '10.226.11.1')).toBe(false); }); test('high-octet subnet — no sign-bit bug', () => { // Arithmetic fixture, not a sim address: a first octet >= 128 is what trips // the sign bit, and no address in the sim plan has one any more. expect(subnetContains('200.100.50.0/24', '200.100.50.253')).toBe(true); expect(subnetContains('200.100.50.0/24', '200.100.51.1')).toBe(false); }); }); describe('classifyIp', () => { test('managed zones (the segmented ones)', () => { expect(classifyIp('10.226.10.10')).toEqual({ kind: 'zone', zone: 'dmz' }); // caddy expect(classifyIp('10.226.20.30')).toEqual({ kind: 'zone', zone: 'app' }); // authentik expect(classifyIp('10.226.30.5')).toEqual({ kind: 'zone', zone: 'secure' }); }); test('internal zone (the LAN — natIp lives here)', () => { expect(classifyIp('10.226.1.253')).toEqual({ kind: 'zone', zone: 'internal' }); }); test('public: the CGNAT stand-in and real public space', () => { expect(classifyIp('203.0.113.100')).toEqual({ kind: 'public' }); // sim firewall external expect(classifyIp('8.8.8.8')).toEqual({ kind: 'public' }); expect(classifyIp('71.36.115.155')).toEqual({ kind: 'public' }); // the prod DDNS target }); test('reserved ranges (incl. the 0.0.0.0 sentinel cheat)', () => { expect(classifyIp('0.0.0.0').kind).toBe('reserved'); expect(classifyIp('127.0.0.1').kind).toBe('reserved'); expect(classifyIp('169.254.1.1').kind).toBe('reserved'); expect(classifyIp('224.0.0.1').kind).toBe('reserved'); }); test('invalid input', () => { expect(classifyIp('nope').kind).toBe('invalid'); expect(classifyIp('256.0.0.1').kind).toBe('invalid'); }); }); describe('isSegmentedZoneIp', () => { test('dmz/app/secure are segmented; internal and public are not', () => { expect(isSegmentedZoneIp('10.226.10.10')).toBe(true); // caddy DMZ — the ISS-0101/0111 bug value expect(isSegmentedZoneIp('10.226.20.30')).toBe(true); expect(isSegmentedZoneIp('10.226.1.253')).toBe(false); // internal IS the LAN expect(isSegmentedZoneIp('203.0.113.100')).toBe(false); // public }); }); describe('isPublicLeakSafe (CLAUDE.md inviolable rule #1)', () => { test('only public addresses are leak-safe', () => { expect(isPublicLeakSafe('203.0.113.100')).toBe(true); expect(isPublicLeakSafe('8.8.8.8')).toBe(true); expect(isPublicLeakSafe('10.226.10.10')).toBe(false); // managed segmented zone expect(isPublicLeakSafe('10.226.1.253')).toBe(false); // internal/RFC-1918 must not leak expect(isPublicLeakSafe('0.0.0.0')).toBe(false); // reserved sentinel }); test('the ISS-0101 scenario: a fronted name resolving to caddy DMZ IP is NOT leak-safe', () => { // www. from a LAN device must be the natIp (internal) or public, never // caddy's DMZ container IP. The doctor + e2e share this judgment. const wwwResolvedWrong = '10.226.10.10'; expect(isSegmentedZoneIp(wwwResolvedWrong)).toBe(true); expect(classifyIp('10.226.1.253').kind).toBe('zone'); // the RIGHT answer (natIp, internal) }); });