Security¶
Threat model¶
- Rate limiting is a mitigation, not a complete defense. It reduces
the rate at which an attacker can act, but a sufficiently patient or
distributed attacker (many IPs, many accounts) can still eventually
exceed what a single-dimension limit catches. Combine key functions
(
combine("ip", "user")) or add IP-based limits alongside user-based ones for defense in depth against credential-stuffing-style attacks that rotate identities. by_iptrustsX-Forwarded-For. If your app is directly exposed to the internet (not behind a trusted proxy/load balancer that sanitizes this header), a client can forgeX-Forwarded-Forto evade IP-based limiting entirely. Only rely onby_ipbehind infrastructure you trust to set that header correctly (or write a custom key function reading whatever header your actual proxy sets, e.g.True-Client-IPor a signed header).- Rate-limit state is visible to anyone with Redis access. Keys are
prefixed (
KEY_PREFIX) but not encrypted — treat your Redis instance with the same access controls you'd apply to any other operationally-sensitive store. An identity string derived fromby_user/by_tenantcould reveal user/tenant IDs to anyone who can read Redis keys directly (not to API clients, who never see raw keys). - A misconfigured
costcallable can be a denial-of-service vector against your own users. Ifcost=lambda request: ...reads attacker-controlled input without bounds (e.g. an unvalidated "count" field), a malicious request could claim an enormous cost, exhausting a shared budget for other users under the same key. Validate/clamp any request-derived cost value. - This package doesn't rate-limit at the network/connection level.
A sufficiently large flood of distinct identities (e.g. a botnet with
thousands of IPs, each under the per-IP threshold) isn't caught by a
per-identity limiter — that's a job for infrastructure-level DDoS
protection (a CDN, a WAF,
iptables/nftablesrate limiting), not an application-layer library like this one.
Recommended production configuration¶
RATELIMIT_PLUS = {
"REDIS_URL": "redis://ratelimit-redis.internal:6379/3", # dedicated instance/DB
}
- Use a Redis instance/logical DB dedicated to rate limiting, separate from your caching/session store, so a rate-limiting incident (e.g. unexpectedly high key volume) can't degrade unrelated Redis usage.
- Set conservative default limits on authentication and password-reset endpoints specifically (see Common Patterns).
- Prefer
key="user"(falls back to IP for anonymous requests) over barekey="ip"wherever authentication is available, since it's harder for a single attacker to acquire many distinct authenticated accounts than many distinct IPs.
Reporting a vulnerability¶
See SECURITY.md.