mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-23 00:41:40 +00:00
fix(proxy): resolve the login rate limit kill switch once per process
Reading LITELLM_DISABLE_LOGIN_RATE_LIMIT through get_secret_bool on every unauthenticated sign-in attempt meant a hosted secret manager in read mode was queried once per password guess, before any counter was checked Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
82902e83c2
commit
ec9a926e84
2 changed files with 32 additions and 1 deletions
|
|
@ -76,6 +76,12 @@ def warn_login_counters_are_per_worker(num_workers: str) -> None:
|
|||
)
|
||||
|
||||
|
||||
@cache
|
||||
def _rate_limit_disabled() -> bool:
|
||||
"""Resolved once per process so an unauthenticated flood never reaches the secret manager."""
|
||||
return bool(get_secret_bool("LITELLM_DISABLE_LOGIN_RATE_LIMIT", False))
|
||||
|
||||
|
||||
async def _sleep(seconds: float) -> None:
|
||||
"""The wait a rejected sign-in is held for. Replaced in tests so the suite pays no wall clock."""
|
||||
await asyncio.sleep(seconds)
|
||||
|
|
@ -161,7 +167,7 @@ class LoginThrottle:
|
|||
username_cache=_FAILED_LOGIN_USERNAME_CACHE,
|
||||
source_cache=_FAILED_LOGIN_SOURCE_CACHE,
|
||||
redis_cache=redis_usage_cache,
|
||||
enabled=not get_secret_bool("LITELLM_DISABLE_LOGIN_RATE_LIMIT", False),
|
||||
enabled=not _rate_limit_disabled(),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
|
|
|||
|
|
@ -1267,6 +1267,31 @@ def test_settings_that_arrive_as_environment_strings_are_honored(monkeypatch):
|
|||
assert throttle.window_seconds == 900, "garbage still falls back to the default"
|
||||
|
||||
|
||||
def test_the_disable_flag_is_read_once_not_per_login_attempt(monkeypatch):
|
||||
"""Regression: the kill switch was read through the secret manager on every unauthenticated request.
|
||||
|
||||
With a hosted secret manager in read mode that is a synchronous network call per guess, so a
|
||||
flood of wrong passwords could exhaust the secret manager even after the source was refused.
|
||||
"""
|
||||
from litellm.proxy import proxy_server as ps
|
||||
from litellm.proxy.auth import login_throttle
|
||||
|
||||
reads: Final[list[str]] = [] # mutable-ok: test-only call recorder
|
||||
monkeypatch.setattr(login_throttle, "get_secret_bool", lambda name, default: reads.append(name) or default)
|
||||
login_throttle._rate_limit_disabled.cache_clear()
|
||||
monkeypatch.setattr(ps, "general_settings", {})
|
||||
request = MagicMock()
|
||||
request.headers = {}
|
||||
request.client = MagicMock()
|
||||
request.client.host = "1.2.3.4"
|
||||
|
||||
for _ in range(50):
|
||||
assert login_throttle.LoginThrottle.from_request(request).enabled is True
|
||||
|
||||
login_throttle._rate_limit_disabled.cache_clear()
|
||||
assert reads == ["LITELLM_DISABLE_LOGIN_RATE_LIMIT"]
|
||||
|
||||
|
||||
def test_a_negative_or_boolean_setting_falls_back_to_the_default(monkeypatch):
|
||||
"""A limit below one would refuse everyone; a bool is a typo, not a count."""
|
||||
from litellm.proxy import proxy_server as ps
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue