Skip to content

fix(rate-limit): key IPv6 clients on their /56 in the flow limiters - #461

Merged
Bccorb merged 1 commit into
mainfrom
fix/rate-limit-ipv6-keys
Oct 21, 2026
Merged

Bccorb merged 1 commit into
mainfrom
fix/rate-limit-ipv6-keys

Conversation

@Bccorb

@Bccorb Bccorb commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

The magic link, OTP and OAuth flow limiters have custom key generators. When a request carries no email or phone (and always, for the OAuth flow), they fell back to the full req.ip. A host typically holds a whole IPv6 /64, so it could rotate addresses and get a fresh bucket each time. express-rate-limit warns about exactly this at runtime (ERR_ERL_KEY_GEN_IPV6), and its own default key generator already groups IPv6 by /56. So the general limiter, the per-IP flow limiters, slow-down and the JWKS limiter were not affected.

The fallback now goes through the library's ipKeyGenerator (/56, the library default). IPv4 and IPv4-mapped addresses are keyed as before, and email- or phone-keyed requests are unchanged. Account lockout is per account in Postgres and was never affected.

Found while writing the production checklist (fells-code/seamless-auth-docs#121).

Checks: npm run typecheck, npm run lint and Prettier pass. npm run test:run passes 1877 tests (1 todo), including a new case showing rotated addresses in one /56 share a key across all three generators. npm run coverage meets the thresholds (lines 99.16%). CI's audit step needs #456 first.

The magic link, OTP and OAuth flow key generators fell back to the full client
address, so a client holding an IPv6 range got a fresh bucket per address.
@Bccorb Bccorb added the v0.19 Targets the v0.19 release label Oct 20, 2026
@Bccorb
Bccorb merged commit 4ec6933 into main Oct 21, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v0.19 Targets the v0.19 release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant