All checks were successful
check / check (push) Successful in 2m53s
Rate-limit keys were per-address, i.e. per /128 for IPv6. A routed /64 is the normal residential and mobile IPv6 allocation, so a client could rotate source addresses inside its own prefix and mint a fresh bucket per request, evading every limiter here at the network layer with no spoofing and nothing to detect. The shared key function now reduces the client address to a bucket by family: IPv4 keys on the full address, IPv6 on its /64 prefix. All four limiter instances (login, password change, and the receiver's per-entrypoint and aggregate limits) go through that one function, so all of them are covered. Both branches of that function are covered by tests: the direct-peer branch, and the trusted-proxy branch that takes the client address out of X-Forwarded-For. The second is the one a production deployment takes, since it is required to run behind a reverse proxy with TRUSTED_PROXIES set. IPv4-mapped addresses (::ffff:1.2.3.4) key as the IPv4 address they carry rather than being masked, which would otherwise collapse every IPv4 client behind a mapping proxy into the ::ffff:0:0/96 bucket. An unparseable RemoteAddr still keys on its raw value, so those stay in distinct buckets instead of collapsing together. No new configuration surface.