fix(security): normalize IPv6 in rate-limit keyGenerator (ADR-0021) #260
Reference in New Issue
Block a user
Delete Branch "fix/bff-rate-limit-ipv6-key"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Fix
ERR_ERL_KEY_GEN_IPV6raised byexpress-rate-limitat BFF boot: the rate-limit middleware's customkeyGeneratorwas usingreq.ipverbatim, which the library v8 refuses because it lets an IPv6 attacker rotate through the host bits of their own subnet to escape per-IP rate-limiting. Surfaced during the ADR-0030 dockerised dev mode validation (the boot log shows theValidationErrorimmediately after the rate-limit middleware is built).Root cause
apps/portal-bff/src/security/rate-limit.middleware.tsreturned:req.ipis the raw address the IP-trust chain hands Express. For IPv4 that's already the right bucket key. For IPv6, every distinct host in an attacker's allocation hashes to a different bucket — even though the same human controls all of them. An attacker on a residential IPv6 assignment (typically/56) thus has ~2^72 trivially-rotatable buckets per/56, which makes the per-IP rate limit useless against them.express-rate-limitv7+ ships anipKeyGeneratorhelper that truncates the address to its/56prefix before keying. The library v8 raisesERR_ERL_KEY_GEN_IPV6at boot if a customkeyGeneratorreturnsreq.ipverbatim, precisely to refuse shipping this bypass.Fix
apps/portal-bff/src/security/rate-limit.middleware.tsipKeyGeneratorfromexpress-rate-limit; wrapreq.ipthrough it when keying. IPv4 addresses pass through unchanged; IPv6 addresses get truncated to their/56. Comment explains the rationale + that the lib's/56default matches a typical residential ISP customer allocation.apps/portal-bff/src/security/rate-limit.middleware.spec.ts/56share a bucket (the bypass would have set both apart), and that distinct/56s remain isolated (truncation does not collapse all IPv6 traffic into one global bucket). Existing IPv4 / session /SKIP_PATHStests are unchanged and still pass —ipKeyGeneratoris a no-op for IPv4.The session-keyed branch (
s:${sessionID}) is untouched: sessions key on the BFF-issued session id, not the address.Why the BFF kept booting despite the error
The log shows
bootstrapreachingAuthModuleimmediately after theValidationErrorprintout.express-rate-limitv8'serrorHandlerdefaults to logging the error and continuing rather than throwing for this specific validation, so the middleware was effectively running with the unfixedkeyGeneratoruntil now — i.e. the bypass was live in the dev BFF. Fixed pre-emptively, before any prod consumer.Related
/api/auth/meproxy errors visible in the same log run were a side effect of the BFF restarting on every config validator until the env was fully populated; unrelated to this fix).Test plan
pnpm exec nx test portal-bff --testFile=apps/portal-bff/src/security/rate-limit.middleware.spec.ts— 794 tests pass, including the new/56isolation case../infra/local/dev.sh restart portal-bff) —ERR_ERL_KEY_GEN_IPV6no longer appears in the boot log.ipKeyGeneratoris identity for v4).