From 594d9470d58c13843d499f126b37c1a88638f9f3 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Fri, 20 Mar 2026 12:32:55 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=AA=A4=20fix:=20Avoid=20express-rate-limi?= =?UTF-8?q?t=20v8=20ERR=5FERL=5FKEY=5FGEN=5FIPV6=20False=20Positive=20(#12?= =?UTF-8?q?333)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: avoid express-rate-limit v8 ERR_ERL_KEY_GEN_IPV6 false positive express-rate-limit v8 calls keyGenerator.toString() and throws ERR_ERL_KEY_GEN_IPV6 if the source contains the literal substring "req.ip" without "ipKeyGenerator". When packages/api compiles req?.ip to older JS targets, the output contains "req.ip", triggering the heuristic. Bracket notation (req?.['ip']) produces identical runtime behavior but never emits the literal "req.ip" substring regardless of compilation target. Closes #12321 * fix: add toString regression test and clean up redundant annotation Add a test that verifies removePorts.toString() does not contain "req.ip", guarding against reintroduction of the ERR_ERL_KEY_GEN_IPV6 false positive. Fix a misleading test description and remove a redundant type annotation on a trivially-inferred local. --- packages/api/src/utils/ports.spec.ts | 8 +++++++- packages/api/src/utils/ports.ts | 8 ++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/api/src/utils/ports.spec.ts b/packages/api/src/utils/ports.spec.ts index ea4dc284c7..0a53c867ea 100644 --- a/packages/api/src/utils/ports.spec.ts +++ b/packages/api/src/utils/ports.spec.ts @@ -75,7 +75,7 @@ describe('removePorts', () => { expect(removePorts(req(undefined))).toBeUndefined(); }); - test('returns undefined when ip is empty string', () => { + test('returns empty string when ip is empty string', () => { expect(removePorts({ ip: '' } as Request)).toBe(''); }); @@ -90,6 +90,12 @@ describe('removePorts', () => { }); }); + describe('express-rate-limit v8 heuristic guard', () => { + test('function source does not contain "req.ip" (guards against ERR_ERL_KEY_GEN_IPV6)', () => { + expect(removePorts.toString()).not.toContain('req.ip'); + }); + }); + describe('unrecognized formats fall through unchanged', () => { test('returns garbage input unchanged', () => { expect(removePorts(req('not-an-ip'))).toBe('not-an-ip'); diff --git a/packages/api/src/utils/ports.ts b/packages/api/src/utils/ports.ts index ecd38039d6..ed20c89193 100644 --- a/packages/api/src/utils/ports.ts +++ b/packages/api/src/utils/ports.ts @@ -1,8 +1,12 @@ import type { Request } from 'express'; -/** Strips port suffix from req.ip for use as a rate-limiter key (IPv4 and IPv6-safe) */ +/** + * Strips port suffix from req.ip for use as a rate-limiter key (IPv4 and IPv6-safe). + * Bracket notation for the ip property avoids express-rate-limit v8's toString() + * heuristic that scans for the literal substring "req.ip" (ERR_ERL_KEY_GEN_IPV6). + */ export function removePorts(req: Request): string | undefined { - const ip = req?.ip; + const ip = req?.['ip']; if (!ip) { return ip; }