-
Notifications
You must be signed in to change notification settings - Fork 108
Description
Bug Report: slowapi error handler assumes exc.detail, causing AttributeError with ConnectionError
Summary
When Redis (or another storage backend) is unavailable, slowapi's error handler tries to access exc.detail on a ConnectionError exception, which doesn't have this attribute. This causes a secondary AttributeError that masks the original connection failure and crashes the application.
Environment
- slowapi version: 0.1.9
- Python version: 3.11
- Storage backend: Redis
- Framework: FastAPI 0.115.12 with Starlette 0.46.1
- OS: Linux
Steps to Reproduce
-
Configure slowapi with Redis as the storage backend:
from slowapi import Limiter from slowapi.middleware import SlowAPIMiddleware from slowapi.util import get_remote_address limiter = Limiter( key_func=get_remote_address, storage_uri="redis://localhost:6379", default_limits=["30000/day", "4000/hour"], )
-
Make Redis unavailable (stop Redis service, network issue, or incorrect connection string)
-
Make a request that goes through the rate limiting middleware
-
Observe the
AttributeErrorcrash
Expected Behavior
When the storage backend (Redis) is unavailable, slowapi should:
- Handle the
ConnectionErrorgracefully without crashing - Either return a proper error response (429 or 500) with a meaningful message
- Or fall back to in-memory storage if configured
- Not lose the original exception context
Actual Behavior
The application crashes with an AttributeError because the error handler assumes all exceptions have a detail attribute:
AttributeError: 'ConnectionError' object has no attribute 'detail'
The full stack trace shows the issue occurs in slowapi/extension.py line 81:
File "/src/.venv/lib/python3.11/site-packages/slowapi/middleware.py", line 130, in dispatch
error_response, should_inject_headers = sync_check_limits(
^^^^^^^^^^^^^^^^^^
File "/src/.venv/lib/python3.11/site-packages/slowapi/middleware.py", line 77, in sync_check_limits
return exception_handler(request, exc), _bool # type: ignore
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/src/.venv/lib/python3.11/site-packages/slowapi/extension.py", line 81, in _rate_limit_exceeded_handler
{"error": f"Rate limit exceeded: {exc.detail}"}, status_code=429
^^^^^^^^^^
AttributeError: 'ConnectionError' object has no attribute 'detail'
Root Cause
The error handler in slowapi/extension.py (line 81) directly accesses exc.detail without checking if the attribute exists:
{"error": f"Rate limit exceeded: {exc.detail}"}, status_code=429When a ConnectionError (or other exception types) is raised during rate limit checking, this assumption fails because ConnectionError doesn't have a detail attribute.
Suggested Fix
Modify the error handler to safely access the detail attribute:
# Current (line 81 in extension.py):
{"error": f"Rate limit exceeded: {exc.detail}"}, status_code=429
# Suggested fix:
detail = getattr(exc, 'detail', None) or str(exc)
{"error": f"Rate limit exceeded: {detail}"}, status_code=429Or handle ConnectionError specifically:
if isinstance(exc, RateLimitExceeded):
detail = exc.detail
elif isinstance(exc, ConnectionError):
detail = f"Storage backend unavailable: {str(exc)}"
else:
detail = getattr(exc, 'detail', str(exc))
return JSONResponse(
{"error": f"Rate limit exceeded: {detail}"},
status_code=429
)Workaround
As a temporary workaround, we've overridden the private _rate_limit_exceeded_handler attribute:
def safe_rate_limit_exceeded_handler(request: Request, exc: Exception):
if isinstance(exc, RateLimitExceeded):
detail = getattr(exc, 'detail', 'Rate limit exceeded')
return JSONResponse(
{"error": f"Rate limit exceeded: {detail}"},
status_code=429
)
if isinstance(exc, ConnectionError):
logger.error("Rate limiter Redis connection failed", exc_info=True)
return JSONResponse(
{"error": "Rate limiter service unavailable"},
status_code=500
)
detail = getattr(exc, 'detail', str(exc))
return JSONResponse(
{"error": f"Rate limit check failed: {detail}"},
status_code=500
)
limiter._rate_limit_exceeded_handler = safe_rate_limit_exceeded_handler # type: ignore[attr-defined]Related Issues
This appears to be related to issue #213: "Real traceback is lost in case of any internal error (AttributeError: 'XXX' object has no attribute 'detail')"
Additional Context
This bug affects production applications where Redis might be temporarily unavailable. Instead of gracefully handling the storage failure, the application crashes, potentially causing downtime. The fix should be straightforward and would improve the robustness of slowapi's error handling.