From 65b508c22a2b2d478a24b6cbb1ee2a77321a02be Mon Sep 17 00:00:00 2001 From: Luca Seemann Date: Fri, 17 Jul 2026 16:31:06 +0200 Subject: [PATCH] Fix custom CLIENT_IP_HEADER not parsing x-forwarded-for/forwarded chains --- src/lib/detect.test.ts | 8 ++++++++ src/lib/ip.ts | 30 ++++++++++++++++-------------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/lib/detect.test.ts b/src/lib/detect.test.ts index 804c84029..8b66e06f7 100644 --- a/src/lib/detect.test.ts +++ b/src/lib/detect.test.ts @@ -24,6 +24,14 @@ test('getIpAddress: Custom header', () => { expect(getIpAddress(new Headers({ 'x-custom-ip-header': IP }))).toEqual(IP); }); +test('getIpAddress: Custom header set to x-forwarded-for uses leftmost IP in chain', () => { + process.env.CLIENT_IP_HEADER = 'x-forwarded-for'; + + expect( + getIpAddress(new Headers({ 'x-forwarded-for': `${IP}, 10.0.0.1, 10.0.0.2` })), + ).toEqual(IP); +}); + test('getIpAddress: CloudFlare header', () => { expect(getIpAddress(new Headers({ 'cf-connecting-ip': IP }))).toEqual(IP); }); diff --git a/src/lib/ip.ts b/src/lib/ip.ts index 075a481f5..efe94d09e 100644 --- a/src/lib/ip.ts +++ b/src/lib/ip.ts @@ -58,11 +58,25 @@ function resolveIp(ip?: string | null) { } } +function parseHeaderValue(header: string, value: string) { + if (header === 'x-forwarded-for') { + return resolveIp(value?.split(',')?.[0]?.trim()); + } + + if (header === 'forwarded') { + const match = value.match(/for=(\[?[0-9a-fA-F:.]+]?)/); + + return match ? resolveIp(match[1]) : undefined; + } + + return resolveIp(value); +} + export function getIpAddress(headers: Headers) { const customHeader = process.env.CLIENT_IP_HEADER; if (customHeader && headers.get(customHeader)) { - return resolveIp(headers.get(customHeader)); + return parseHeaderValue(customHeader.toLowerCase(), headers.get(customHeader)); } const header = IP_ADDRESS_HEADERS.find(name => headers.get(name)); @@ -70,19 +84,7 @@ export function getIpAddress(headers: Headers) { return undefined; } - const ip = headers.get(header); - - if (header === 'x-forwarded-for') { - return resolveIp(ip?.split(',')?.[0]?.trim()); - } - - if (header === 'forwarded') { - const match = ip.match(/for=(\[?[0-9a-fA-F:.]+]?)/); - - return match ? resolveIp(match[1]) : undefined; - } - - return resolveIp(ip); + return parseHeaderValue(header, headers.get(header)); } export function stripPort(ip?: string | null) {