Files
FabledScribe/src/fabledassistant/rate_limit.py
T
bvandeusen 00643c778e security: fix 10 vulnerabilities from security audit
- SSRF: block private/internal URLs in image cache fetch
- SSRF: block private/internal URLs in RSS feed fetch (scheme guard)
- SSRF: block private/internal URLs in CalDAV URL setting
- Auth: require login for GET /api/images/<id> (was unauthenticated)
- Auth: restrict Ollama model pull/delete to admin users only
- Info disclosure: remove email from /api/users/search response
- OAuth: skip email-based account linking when email_verified is false
- Config: raise hard error on default SECRET_KEY when SECURE_COOKIES=true
- Rate limit: document proxy header requirement; add startup warning
- XSS: remove src/alt from global DOMPurify ADD_ATTR allowlist

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 00:37:13 -04:00

28 lines
971 B
Python

"""In-process sliding-window rate limiter.
IMPORTANT — deployment note:
Rate limit counters are stored in memory and are lost on process restart.
When deployed behind a reverse proxy (nginx, Caddy, Traefik) you MUST set
TRUST_PROXY_HEADERS=true so that the real client IP is used as the bucket key
rather than the proxy's IP (which would cause all users to share one bucket).
"""
import asyncio
import time
from collections import defaultdict
_buckets: dict[str, list[float]] = defaultdict(list)
_lock = asyncio.Lock()
async def is_rate_limited(key: str, max_requests: int, window_seconds: int) -> bool:
"""Returns True if request should be blocked (limit exceeded)."""
async with _lock:
now = time.monotonic()
cutoff = now - window_seconds
_buckets[key] = [t for t in _buckets[key] if t > cutoff]
if len(_buckets[key]) >= max_requests:
return True
_buckets[key].append(now)
return False