bug(server): no rate limiting on LLM-calling endpoints (/memories, /search, /generate-instructions)
Component: REST API
Summary
server/ already wires up a slowapi Limiter (server/rate_limit.py) and uses it on three endpoints — POST /auth/register (5/minute), POST /auth/login (10/minute), POST /auth/refresh (20/minute), all in server/routers/auth.py. Every other endpoint in server/main.py, including the ones that make a paid call to whatever LLM/embedding provider the deployer configured, has no rate limit at all.
Concretely, these authenticated endpoints in server/main.py are uncapped:
POST /memories(add_memory) — callsMemory.add(...).inferdefaults toTrue(seemem0/memory/main.pyMemory.add), so by default every call runs an LLM fact-extraction pass plus embeddings.POST /search(search_memories) — callsMemory.search(...), which embeds the query and, depending on config (reranker,explain), can also call the LLM.POST /generate-instructions(generate_instructions) — callsllm.generate_response(...)directly on every single request. There's no persistence and no other cost gate; each call is a pure, billed LLM completion.
All three only require Depends(verify_auth) — a valid JWT, a valid per-user X-API-Key, the legacy ADMIN_API_KEY, or nothing at all when the deployer set AUTH_DISABLED=true (which the docs call out as something people do for local dev, docs/open-source/features/rest-api.mdx line 136/196). None of those paths currently hit any rate limiter.
Steps to Reproduce
Not runnable as a script — this is a static gap, confirmed by reading the source rather than by driving a live deployment (I don't have an LLM provider key wired into a running instance in this environment, see "How You Verified This" below).
grep -rn "limiter.limit" server/
# server/routers/auth.py:95 (register)
# server/routers/auth.py:126 (login)
# server/routers/auth.py:145 (refresh)
# -> nothing under server/main.py
With those three call sites as the only guarded routes, an actor holding any valid credential (a leaked/rotated-late API key, a low-trust JWT, or simply AUTH_DISABLED=true) can call POST /generate-instructions, POST /memories, or POST /search in a tight loop with no server-side backstop, driving unbounded billed usage against the deployer's own configured OpenAI/Anthropic/Gemini key.
Expected Behavior
The same per-route @limiter.limit(...) pattern already used in server/routers/auth.py extends to the endpoints in server/main.py that trigger a paid LLM or embedding call per request, so a single credential can't be used to run up unbounded provider spend against the deployer's account. (GET/other read-only, non-LLM endpoints are out of scope for this — the concern is specifically the provider-billed paths.)
Actual Behavior
No rate limit is applied. Nothing in server/main.py imports or uses limiter at all.
Environment
- File:
server/main.py,server/rate_limit.py,server/routers/auth.py - Same
slowapidependency already in use elsewhere inserver/
How You Verified This
What I Ran
grep -rn "limiter" server/ and grep -rn "@limiter.limit" server/, then read every route handler in server/main.py end to end to confirm none of them declare a dependency on, or decorator from, rate_limit.limiter. Cross-checked mem0/memory/main.py's Memory.add signature to confirm infer: bool = True is the real default (so POST /memories calls the LLM unless the caller explicitly passes infer: false).
What I Saw
server/rate_limit.py defines exactly one Limiter(key_func=get_remote_address). The only three usages of @limiter.limit(...) in the whole server/ tree are the three lines in server/routers/auth.py cited above. server/main.py does not import limiter or rate_limit at all.
Why This Is a Bug
The project has already accepted that this class of endpoint needs a server-side rate limit — that's exactly what the three @limiter.limit(...) calls on the auth routes are for. But those three are the free endpoints (password check, token refresh); the endpoints that actually spend the deployer's own LLM/embedding budget on every call are the ones left unguarded. A closed PR on this repo (#3567) was rejected for the library with "this feature is not for the repo as it's handled on the server side" — which is a reasonable position for the importable mem0 package, but server/ is "the server side" the maintainer meant, and it already ships the exact mechanism (slowapi) that PR proposed reinventing. This issue asks only to point the existing mechanism at the three routes that call out to a paid provider per request, not to add a new rate-limiting system.
What I Ruled Out
- Not covered by infrastructure by default:
server/docker-compose.yamlputs the FastAPI app directly behind the published port with no reverse proxy or gateway in front of it, so there's nothing else in the shipped Compose stack that would rate-limit these calls. - Not mitigated by auth alone:
verify_authconfirms identity, not request volume — a valid per-user API key or JWT (orAUTH_DISABLED=true) is sufficient to hit any of the three endpoints as many times as the caller wants. - Not the same as the existing "batch embedding rate limit" discussion (#6270 / PR #6628, since closed) — those are about the SDK backing off against the provider's 429s on outbound calls; this issue is about the server's own inbound surface having no cap at all, independent of whether the provider itself would eventually throttle.
AI Assistance
AI found and wrote this, and I have not reproduced it against a live, running deployment (no LLM provider credentials wired into an instance in this environment). The claim itself — that no rate-limit decorator wraps these three route functions — was confirmed by direct, complete reading of server/main.py and server/rate_limit.py and by grepping the whole server/ tree for every @limiter.limit call site, which is a deterministic, non-ambiguous check (a decorator either wraps a function or it doesn't); what I have not done is drive real repeated HTTP traffic at a booted instance to watch it accept requests unbounded end to end.
Source: mem0ai/mem0