From f69f76b3b1ac39e48ad2d691c45f1f601515105e Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Thu, 11 Jun 2026 19:01:04 -0700 Subject: [PATCH] Handle invalid IPs and localhost lookup failures in location detection --- src/lib/detect.test.ts | 57 +++++++++++++++++++++++++++++++++++++++++- src/lib/detect.ts | 10 +++++++- 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/src/lib/detect.test.ts b/src/lib/detect.test.ts index 6aaab223b..804c84029 100644 --- a/src/lib/detect.test.ts +++ b/src/lib/detect.test.ts @@ -1,8 +1,23 @@ -import { expect, test } from 'vitest'; +import { beforeEach, expect, test, vi } from 'vitest'; +import { getLocation, hasBlockedIp } from './detect'; import { getIpAddress } from './ip'; const IP = '127.0.0.1'; +const isLocalhost = vi.mocked(await import('is-localhost-ip')); + +vi.mock('is-localhost-ip', () => ({ + default: vi.fn(), +})); + +beforeEach(() => { + vi.resetAllMocks(); + + delete process.env.CLIENT_IP_HEADER; + delete process.env.IGNORE_IP; + delete process.env.SKIP_LOCATION_HEADERS; +}); + test('getIpAddress: Custom header', () => { process.env.CLIENT_IP_HEADER = 'x-custom-ip-header'; @@ -20,3 +35,43 @@ test('getIpAddress: Standard header', () => { test('getIpAddress: No header', () => { expect(getIpAddress(new Headers())).toEqual(undefined); }); + +test('getLocation: returns null for malformed ip', async () => { + await expect( + getLocation( + 'not-an-ip', + new Headers({ + 'cf-ipcountry': 'US', + 'cf-region-code': 'CA', + 'cf-ipcity': 'Los Angeles', + }), + false, + ), + ).resolves.toEqual(null); +}); + +test('getLocation: treats localhost check errors as non-local', async () => { + isLocalhost.default.mockRejectedValue(new Error('DNS Lookup failed.')); + + await expect( + getLocation( + '8.8.8.8', + new Headers({ + 'cf-ipcountry': 'US', + 'cf-region-code': 'CA', + 'cf-ipcity': 'Los Angeles', + }), + false, + ), + ).resolves.toEqual({ + country: 'US', + region: 'US-CA', + city: 'Los Angeles', + }); +}); + +test('hasBlockedIp: returns false for malformed client ip with cidr block', () => { + process.env.IGNORE_IP = '10.0.0.0/8'; + + expect(hasBlockedIp('not-an-ip')).toBe(false); +}); diff --git a/src/lib/detect.ts b/src/lib/detect.ts index fb67571d9..fb36ba212 100644 --- a/src/lib/detect.ts +++ b/src/lib/detect.ts @@ -76,11 +76,19 @@ function decodeHeader(s: string | undefined | null): string | undefined | null { return Buffer.from(s, 'latin1').toString('utf-8'); } +async function isLocalIp(ip: string) { + try { + return await isLocalhost(ip); + } catch { + return false; + } +} + export async function getLocation(ip: string = '', headers: Headers, skipHeaders: boolean) { const cleanIp = stripPort(ip); // Ignore local or invalid ips - if (!cleanIp || !ipaddr.isValid(cleanIp) || (await isLocalhost(cleanIp))) { + if (!cleanIp || !ipaddr.isValid(cleanIp) || (await isLocalIp(cleanIp))) { return null; }