From f30966a77c9a26ec2af849226911a51195645dbe Mon Sep 17 00:00:00 2001 From: Matteo Benedetto Date: Sun, 16 Aug 2026 19:22:18 +0200 Subject: [PATCH] feat: rate limit leggero (30/min per IP) su /v1/status pubblico MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - endpoint pubblico per healthcheck, protetto da abusi - verificato sul server: 36 richieste → 30x200 + 6x429 (istanza di test) --- gateway/main.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/gateway/main.py b/gateway/main.py index 584ae65..03441f4 100644 --- a/gateway/main.py +++ b/gateway/main.py @@ -94,6 +94,10 @@ qdrant = QdrantClient(url=QDRANT_URL, api_key=QDRANT_API_KEY) # Rate limit in-memory: {key: [timestamps]} _ratelimit: dict[str, list[float]] = {} +# Rate limit /v1/status (pubblico, per IP): {ip: [timestamps]} +_STATUS_RATE_LIMIT_PER_MIN = 30 +_status_ratelimit: dict[str, list[float]] = {} + # Idempotency in-memory: {api_key:key: {hash, response, ts}} (TTL 24h) _IDEMPOTENCY_TTL_SECONDS = 24 * 3600 _idempotency: dict[str, dict[str, Any]] = {} @@ -416,7 +420,16 @@ async def meta_overview(key: str = Depends(require_auth)) -> dict: @app.get("/v1/status") -async def status() -> dict: +async def status(request: Request) -> dict: + # Endpoint pubblico (healthcheck): rate limit leggero per IP + ip = request.client.host if request.client else "unknown" + now = time.monotonic() + window = _status_ratelimit.setdefault(ip, []) + window[:] = [t for t in window if now - t < 60] + if len(window) >= _STATUS_RATE_LIMIT_PER_MIN: + raise HTTPException(status_code=429, detail="Rate limit superato") + window.append(now) + info = qdrant.get_collection(COLLECTION) return { "status": "ok",