diff --git a/__tests__/door-logic.test.ts b/__tests__/door-logic.test.ts index a09ea01..268edff 100644 --- a/__tests__/door-logic.test.ts +++ b/__tests__/door-logic.test.ts @@ -44,6 +44,18 @@ describe('isBlockedDoorIp (resolved-IP DNS-rebind guard)', () => { expect(isBlockedDoorIp(ip)).toBe(true) } }) + it('blocks the HEX IPv4-mapped form (::ffff:HHHH:HHHH) too', () => { + // ::ffff:a9fe:a9fe == 169.254.169.254 (IMDS); ::ffff:7f00:1 == 127.0.0.1 + for (const ip of ['::ffff:a9fe:a9fe', '::ffff:7f00:1', '::ffff:7f00:0001', '::ffff:a9fe:0102', '[::ffff:7f00:1]']) { + expect(isBlockedDoorIp(ip)).toBe(true) + } + }) + it('ALLOWS non-blocked ranges in HEX IPv4-mapped form', () => { + // ::ffff:c0a8:0109 == 192.168.1.9 ; ::ffff:cb00:7107 == 203.0.113.7 + for (const ip of ['::ffff:c0a8:0109', '::ffff:cb00:7107']) { + expect(isBlockedDoorIp(ip)).toBe(false) + } + }) it('ALLOWS RFC1918 / LAN / ULA / public (door controllers live there)', () => { for (const ip of ['10.0.0.5', '172.16.4.4', '192.168.1.50', '203.0.113.7', 'fd12:3456::1', '2001:db8::1', '::ffff:192.168.1.9']) { expect(isBlockedDoorIp(ip)).toBe(false) diff --git a/lib/door-logic.ts b/lib/door-logic.ts index e8168f4..4b9ce33 100644 --- a/lib/door-logic.ts +++ b/lib/door-logic.ts @@ -69,6 +69,15 @@ export function isBlockedDoorIp(ip: string): boolean { let h = ip.trim().toLowerCase().replace(/^\[|\]$/g, '') const mapped = h.match(/^::ffff:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/) if (mapped) h = mapped[1] + // Hex IPv4-mapped form (::ffff:HHHH:HHHH, e.g. ::ffff:a9fe:a9fe == IMDS): + // decode the two 16-bit groups to a 32-bit IPv4 and run the same octet + // checks below, so it can't slip past the dotted-form guard. + const mappedHex = h.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/) + if (mappedHex) { + const hi = parseInt(mappedHex[1], 16) + const lo = parseInt(mappedHex[2], 16) + h = `${hi >> 8}.${hi & 0xff}.${lo >> 8}.${lo & 0xff}` + } if (h.includes(':')) { if (h === '::1' || h === '::' || h === '0:0:0:0:0:0:0:0') return true if (h === 'fd00:ec2::254') return true // AWS IPv6 IMDS