Jordancole21's picture
Deploy native Gradio SEO page and tool
7e2c39a verified
Raw
History Blame Contribute Delete
3.06 kB
"""Abuse controls for AI-powered Spaces.
Layers (see docs/rate-limiting.md):
1. Per-IP sliding window (server-side, survives incognito/new sessions).
2. Global daily cap β€” cannot be bypassed by identity rotation; bounds
worst-case daily spend to DAILY_CAP x cost-per-generation.
3. Queue concurrency (configured on the Blocks) bounds burn rate.
4. api_name=False on generate events removes the scripted-API surface.
State is in-memory and resets on Space restart; that is acceptable for
abuse control (not accounting).
"""
import os
import threading
import time
from collections import defaultdict
class RateLimiter:
def __init__(
self,
per_ip_limit: int = int(os.environ.get("LF_IP_LIMIT", 8)),
window_seconds: int = int(os.environ.get("LF_IP_WINDOW_SECONDS", 3600)),
daily_cap: int = int(os.environ.get("LF_DAILY_CAP", 500)),
):
self.per_ip_limit = per_ip_limit
self.window = window_seconds
self.daily_cap = daily_cap
self._hits: dict[str, list[float]] = defaultdict(list)
self._day: str = ""
self._day_count = 0
self._lock = threading.Lock()
@staticmethod
def client_ip(request) -> str:
"""Real client IP. On HF Spaces the app sits behind a proxy, so the
client is the first entry of x-forwarded-for, not request.client."""
if request is None:
return "unknown"
fwd = dict(request.headers).get("x-forwarded-for", "")
if fwd:
return fwd.split(",")[0].strip()
try:
return request.client.host
except Exception:
return "unknown"
def check(self, request) -> tuple[bool, str]:
"""Returns (allowed, user_safe_message)."""
ip = self.client_ip(request)
now = time.time()
today = time.strftime("%Y-%m-%d", time.gmtime(now))
with self._lock:
if today != self._day:
self._day, self._day_count = today, 0
if self._day_count >= self.daily_cap:
return False, (
"The forge is cooling down for today β€” daily generation "
"limit reached. Come back tomorrow!"
)
window_start = now - self.window
self._hits[ip] = [t for t in self._hits[ip] if t > window_start]
if len(self._hits[ip]) >= self.per_ip_limit:
minutes = max(1, int((self._hits[ip][0] + self.window - now) / 60))
return False, (
f"You've hit the hourly generation limit. Try again in about "
f"{minutes} minute{'s' if minutes != 1 else ''}."
)
self._hits[ip].append(now)
self._day_count += 1
# Opportunistic cleanup so the dict doesn't grow unbounded.
if len(self._hits) > 10000:
stale = [k for k, v in self._hits.items() if not v or v[-1] < window_start]
for k in stale:
del self._hits[k]
return True, ""