Skip to content

Bug Report: slowapi error handler assumes exc.detail, causing AttributeError with ConnectionError #253

@rasadov

Description

@rasadov

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

  1. 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"],
    )
  2. Make Redis unavailable (stop Redis service, network issue, or incorrect connection string)

  3. Make a request that goes through the rate limiting middleware

  4. Observe the AttributeError crash

Expected Behavior

When the storage backend (Redis) is unavailable, slowapi should:

  • Handle the ConnectionError gracefully 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=429

When 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=429

Or 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions