Browse Source

Remove IP exemption list from rate limiter

RATE_LIMIT_WHITELIST_IPS setting, self.whitelist bypass checks in
RateLimitMiddleware, and the corresponding test are removed. The
bypass_paths mechanism covers the legitimate high-frequency paths
(/painel, /sessao, /voto-individual); no IP-level exemption is needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
rate-limiter-2026
Edward Ribeiro 1 week ago
parent
commit
8f58d65059
  1. 9
      sapl/middleware/ratelimit.py
  2. 13
      sapl/middleware/test_ratelimiter.py
  3. 8
      sapl/settings.py

9
sapl/middleware/ratelimit.py

@ -300,7 +300,6 @@ class RateLimitMiddleware:
self.get_response = get_response
self.anon_threshold, self.anon_window = _parse_rate(settings.RATE_LIMITER_RATE)
self.auth_threshold, self.auth_window = _parse_rate(settings.RATE_LIMITER_RATE_AUTHENTICATED)
self.allowlist = set(settings.RATE_LIMIT_ALLOWLIST_IPS)
self._rl_cache = caches['ratelimit']
self.not_found_threshold = settings.RATE_LIMIT_404_THRESHOLD
self._bypass_paths = [
@ -314,11 +313,10 @@ class RateLimitMiddleware:
self.api_block_seconds = getattr(settings, 'API_RATE_LIMIT_BLOCK_SECONDS', 300)
self.api_same_origin_bypass = getattr(settings, 'API_RATE_LIMIT_SAME_ORIGIN_BYPASS', True)
logger.info(
'[RATELIMIT] anon=%s auth=%s bot=%s allowlist=%s bypass_paths=%s',
'[RATELIMIT] anon=%s auth=%s bot=%s bypass_paths=%s',
settings.RATE_LIMITER_RATE,
settings.RATE_LIMITER_RATE_AUTHENTICATED,
settings.RATE_LIMITER_RATE_BOT,
list(self.allowlist) or '(none)',
[p.pattern for p in self._bypass_paths] or '(none)',
)
logger.info(
@ -450,9 +448,6 @@ class RateLimitMiddleware:
def _evaluate(self, request):
ip = get_client_ip(request)
if ip in self.allowlist:
return {'action': 'pass', 'ip': ip}
# Check 1: known bad UA (hardcoded Python list — substring match)
ua = request.META.get('HTTP_USER_AGENT', '')
for fragment in BOT_UA_FRAGMENTS:
@ -533,8 +528,6 @@ class RateLimitMiddleware:
user = getattr(request, 'user', None)
if user and user.is_authenticated:
return
if ip in self.allowlist:
return
count = self._incr_with_ttl(RL_IP_404S.format(ip=ip), ttl=self.anon_window)
if count >= self.not_found_threshold:
_set_block(RL_IP_BLOCKED.format(ip=ip), RL_INDEX_BLOCKED_IPS, self.BLOCK_TTL)

13
sapl/middleware/test_ratelimiter.py

@ -62,7 +62,6 @@ def _auth_req(uid=7, **kwargs):
def _make_middleware(
allowlist=None,
anon_rate='35/m',
auth_rate='120/m',
api_rate_limit_enabled=True,
@ -90,7 +89,6 @@ def _make_middleware(
mock_settings.RATE_LIMITER_RATE = anon_rate
mock_settings.RATE_LIMITER_RATE_AUTHENTICATED = auth_rate
mock_settings.RATE_LIMITER_RATE_BOT = '5/m'
mock_settings.RATE_LIMIT_ALLOWLIST_IPS = allowlist or []
mock_settings.RATE_LIMIT_404_THRESHOLD = 20
mock_settings.RATE_LIMIT_BYPASS_PATHS = []
mock_settings.POD_NAMESPACE = _NAMESPACE # keep module-level _NAMESPACE consistent
@ -230,17 +228,6 @@ def test_smart_rate_auth_returns_auth_rate():
assert smart_rate(None, _auth_req()) == '120/m'
# ---------------------------------------------------------------------------
# RateLimitMiddleware — allowlisted IP bypasses everything (including bad UA)
# ---------------------------------------------------------------------------
def test_allowlist_bypasses_all_checks():
mw, mock_cache = _make_middleware(allowlist=['1.2.3.4'])
result = mw._evaluate(_anon_req(ip='1.2.3.4', ua='GPTBot/1.0'))
assert result == {'action': 'pass', 'ip': '1.2.3.4'}
mock_cache.get.assert_not_called()
# ---------------------------------------------------------------------------
# Check 1 — known bot User-Agent
# ---------------------------------------------------------------------------

8
sapl/settings.py

@ -409,14 +409,6 @@ RATE_LIMITER_RATE = config('RATE_LIMITER_RATE', default='120/m')
RATE_LIMITER_RATE_AUTHENTICATED = config('RATE_LIMITER_RATE_AUTHENTICATED', default='240/m')
RATE_LIMITER_RATE_BOT = config('RATE_LIMITER_RATE_BOT', default='5/m')
# Comma-separated IPs exempt from rate limiting (e.g. legislative-house ranges).
# Leave empty until the IP list is available — see rate-limiter-v2.md §9.
RATE_LIMIT_ALLOWLIST_IPS = config(
'RATE_LIMIT_ALLOWLIST_IPS',
default='',
cast=lambda v: [x.strip() for x in v.split(',') if x.strip()],
)
# Seconds between re-fetches of the runtime UA deny list from Redis DB 1.
# Lower values pick up new blocked UAs faster; higher values reduce Redis round-trips.
RATE_LIMITER_UA_BLOCKLIST_REFRESH = config('RATE_LIMITER_UA_BLOCKLIST_REFRESH', default=60, cast=int)

Loading…
Cancel
Save