diff --git a/README.md b/README.md index f826861..8db5099 100644 --- a/README.md +++ b/README.md @@ -2554,7 +2554,7 @@ arrived uncut — never the numbers. Every limiter here — receiver, login, and password change — identifies the client the same way, through one shared key function: the -connection's own address, unless the peer is listed in +connection's own address, unless the peer is inside `TRUSTED_PROXIES`, in which case the forwarded client address is used instead. That address becomes a bucket by family: IPv4 keys on the full address, IPv6 on its `/64` prefix. A routed `/64` is the normal @@ -2592,7 +2592,8 @@ opposite directions: The login `POST` is the one endpoint with no pre-emptive limiter in front of it, and that is deliberate. A limiter that spends budget on -arrival is a lockout in this deployment shape: sharing one bucket, a +arrival is a lockout wherever clients share one bucket, as they do +behind a reverse proxy that `TRUSTED_PROXIES` does not cover: a stranger sending five POSTs a minute — about 0.08 requests per second, from anywhere — keeps it permanently full, and the operator has no second administrative path. So the handler inverts the order: diff --git a/internal/middleware/ratelimit_test.go b/internal/middleware/ratelimit_test.go index 75cdd1a..82a5669 100644 --- a/internal/middleware/ratelimit_test.go +++ b/internal/middleware/ratelimit_test.go @@ -384,8 +384,8 @@ const ( // trustedProxyCIDR is the proxy network the forwarded-path // tests configure, and trustedPeer an address inside it. A // production deployment is required to run behind a reverse - // proxy with TRUSTED_PROXIES set, so this is the shape the - // bucketing has to hold in. + // proxy that TRUSTED_PROXIES covers, either by the default or by + // a set value, so this is the shape the bucketing has to hold in. trustedProxyCIDR = "10.0.0.0/8" trustedPeer = "10.0.0.1:44444" ) @@ -1097,8 +1097,9 @@ func TestPostRateLimit_IPv4IndependentPerAddress(t *testing.T) { // that arrives from trustedPeer — a configured trusted proxy — and // names forwarded as its client in X-Forwarded-For. That is the // production path: a deployment is required to run behind a reverse -// proxy with TRUSTED_PROXIES set, so the forwarded address, not the -// peer, is what the limiters bucket on there. +// proxy that TRUSTED_PROXIES covers, either by the default or by a +// set value, so the forwarded address, not the peer, is what the +// limiters bucket on there. func forwardedKeyFor( t *testing.T, m *middleware.Middleware, forwarded string, ) string { @@ -1178,9 +1179,9 @@ func TestRateLimitKey_ForwardedIPv6BucketsByPrefix(t *testing.T) { // // Every existing test of this fallback uses an IPv4 proxy, where // bucketKey is the identity function, so replacing the call with -// peer.String() leaves the whole suite green. Only operator-listed -// addresses reach this line and the fallback is fail-closed, so this -// pins behaviour rather than fixing a defect. +// peer.String() leaves the whole suite green. Only addresses inside +// TRUSTED_PROXIES reach this line and the fallback is fail-closed, so +// this pins behaviour rather than fixing a defect. func TestRateLimitKey_TrustedPeerUnusableForwardedMasksPeer( t *testing.T, ) {