Add per-IP sliding-window rate limiting to API endpoints #51
Loading…
Reference in a new issue
No description provided.
Delete branch "refs/pull/51/head"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #50
Adds
RateLimitMiddlewareto enforce per-client-IP request limits on all/api/v1/*routes:RATE_LIMIT_BOOTSTRAP_RPM)RATE_LIMIT_API_RPM)Implemented as a pure-Python sliding-window counter with no new dependencies. Eight tests in
tests/test_rate_limiting.pyusing an isolated minimal Starlette app.packages/fenliu/src/fenliu/middleware.pyline 182 — @wuming[bot]✅ Fixed in
dc5bff4— replacedany(path.startswith(p) for p in _BOOTSTRAP_PATHS)with the exact set membership testrequest.url.path in self._BOOTSTRAP_PATHS. A path like/api/v1/api-keys/status-extranow correctly falls through to the general API rate limit bucket.packages/fenliu/src/fenliu/middleware.pyline 185 — @wuming[bot]✅ Fixed in
dc5bff4— same change as above:startswithreplaced withrequest.url.path in self._BOOTSTRAP_PATHS. The note aboutstartswithbeing broader than intended is correct; the fix uses exact set lookup.packages/fenliu/src/fenliu/middleware.pyline 181 — @wuming[bot]🔴 No change —
request.clientis onlyNonein atypical ASGI transports; uvicorn (the sole supported server) always populates it. The"unknown"fallback is a defensive guard for that unreachable edge case, not a production code path. AddingX-Forwarded-Forreading without a trusted-proxy allowlist (see the related HIGH comment) would introduce a spoofing vector that is strictly worse than sharing one bucket for a case that never arises.packages/fenliu/src/fenliu/middleware.pyline 181 — @wuming[bot]🔴 No change — fenliu is deployed as a single-user self-hosted service, not behind a load balancer or shared reverse proxy. Using
request.client.host(the actual transport-layer peer address as reported by uvicorn) is correct and cannot be spoofed via headers. ReadingX-Forwarded-Forwithout a configured trusted-proxy allowlist is the standard recommendation against, precisely because it introduces the spoofing attack this comment describes. If a deployment ever adds a reverse proxy, the right fix is uvicorn's--forwarded-allow-ipsflag or Starlette'sProxyHeadersMiddlewarewith an explicit trusted range — not unconditional header reading inside this middleware.packages/fenliu/src/fenliu/middleware.pyline 138 — @wuming[bot]🔴 No change — the
is_allowedloop already prunes expired timestamps from each deque on every request (while timestamps and timestamps[0] < cutoff: timestamps.popleft()). After 60 seconds of inactivity from an IP the deque becomes empty, so the retained memory per key is just the dict entry and an emptydequeobject (a few tens of bytes). The number of distinct IPs hitting this single-user service is small enough that the residual footprint is negligible. An LRU eviction strategy would be the right approach for a high-traffic multi-tenant deployment, but adds meaningful complexity that isn't warranted here.