feat(B-RL): Rate-Limiting Konsistenz — zentrale Policies für Auth/AI/Upload/Webhook
Check Cross-Plugin Imports / check (push) Has been cancelled

B-RL: Rate-Limiting auf zentrale check_rate_limit() umgestellt
- forgejo_error_reporter: In-Memory → zentrale Redis-Rate-Limit
- ai_proactive: eigene Redis-Logik → zentrale check_rate_limit()
- agent_runner: DB-basiertes Limit bleibt (zählt echte Ausführungen)
- RateLimitPolicy Enum (AUTH/AI/UPLOAD/WEBHOOK) + check_rate_limit_policy()
- 8 neue config.py Settings für Policy-Limits
- 12 Routes mit Policies versehen (login, password-reset, AI, uploads, webhooks)

Tests: 20 Tests in test_rate_limit_policies.py — alle grün
- Policies, check_rate_limit, reset, get_client_ip, forgejo, ai_proactive
- Keine Regression: 116 bestehende Tests grün
This commit is contained in:
Agent Zero
2026-08-13 17:51:04 +02:00
parent 8c04c85d35
commit bb36378494
15 changed files with 516 additions and 84 deletions
@@ -21,10 +21,9 @@ _DEDUP_MAX_SIZE = 100
_DEDUP_TTL_SECONDS = 3600 # 1 hour
# ── Rate limiting ────────────────────────────────────────────────────────────
# Max 10 issues per hour
# Max 10 reports per 5 minutes — enforced via central check_rate_limit()
_RATE_LIMIT_MAX = 10
_RATE_LIMIT_WINDOW = 3600 # 1 hour in seconds
_rate_limit_timestamps: list[float] = []
_RATE_LIMIT_WINDOW = 300 # 5 minutes
# ── Lock for thread safety ──────────────────────────────────────────────────
_lock = asyncio.Lock()
@@ -79,30 +78,6 @@ async def _is_duplicate(dedup_key: str) -> bool:
return False
async def _check_rate_limit() -> bool:
"""Check if we're within the rate limit.
Returns True if request is allowed, False if rate limited.
"""
async with _lock:
now = time.monotonic()
window_start = now - _RATE_LIMIT_WINDOW
# Remove timestamps outside the window
while _rate_limit_timestamps and _rate_limit_timestamps[0] < window_start:
_rate_limit_timestamps.pop(0)
if len(_rate_limit_timestamps) >= _RATE_LIMIT_MAX:
logger.warning(
"Forgejo Error Reporter rate limited: %d issues in the last hour",
len(_rate_limit_timestamps),
)
return False
_rate_limit_timestamps.append(now)
return True
async def _ensure_labels_exist(client: httpx.AsyncClient, settings: dict[str, str]) -> list[int]:
"""Ensure required labels exist in the Forgejo repository.
@@ -178,8 +153,22 @@ async def report_error_to_forgejo(entry: dict[str, Any]) -> bool:
if await _is_duplicate(dedup_key):
return False
# Rate limit check
if not await _check_rate_limit():
# Rate limit check — uses central check_rate_limit() with Redis + in-memory fallback
from app.core.rate_limit import check_rate_limit
from fastapi import HTTPException
try:
await check_rate_limit(
"rate:forgejo_report:global",
_RATE_LIMIT_MAX,
_RATE_LIMIT_WINDOW,
)
except HTTPException:
logger.warning(
"Forgejo Error Reporter rate limited: max %d reports per %d seconds",
_RATE_LIMIT_MAX,
_RATE_LIMIT_WINDOW,
)
return False
# Build issue body