Rate Limiting
Server-side rate limiting on abuse-prone surfaces using the Convex rate-limiter component, configured in one place in rateLimit.ts.
Abuse-prone backend surfaces are rate-limited with the
@convex-dev/rate-limiter
component. Limits are enforced inside Convex, so they cannot be bypassed from
the client. All limits live in one place: rateLimitConfig in
packages/backend/convex/rateLimit.ts.
What is limited
| Limit | Surface | Policy |
|---|---|---|
sensitiveEmailChallengeCreate | Requesting an email verification code for a sensitive action | 3 per 10 minutes, keyed per user + action |
sensitivePasswordConfirm | Wrong-password attempts during step-up | 5 per 15 minutes, keyed per user + action |
generateUploadUrl | Requesting a file-upload URL | Token bucket, 20 per hour with a burst capacity of 5 |
The two sensitive* limits back
Sensitive Action Protection; a 60-second
resend cooldown for email codes is enforced inline in addition to the limit.
How it is used
A limited handler calls the shared limiter and returns a friendly message when the caller is over budget:
import { getRateLimitErrorMessage, rateLimiter } from "./rateLimit";
const limit = await rateLimiter.limit(ctx, "sensitivePasswordConfirm", {
key: `${userId}:${action}`,
});
if (!limit.ok) {
return { ok: false, error: getRateLimitErrorMessage(limit.retryAfter) };
}getRateLimitErrorMessage turns the limiter's retryAfter into a human message
("Please try again in N seconds").
Adding or tuning a limit
- Add an entry to
rateLimitConfigwith akind("fixed window"or"token bucket"),rate, andperiod(use theMINUTE/HOURhelpers).shardsspreads a high-volume global limit across documents. - Call
rateLimiter.limit(ctx, "<name>", { key })in the handler before doing the work, choosing akeythat scopes the limit (per user, per email, etc.). - Return
getRateLimitErrorMessage(retryAfter)on rejection.
The configured names are type-checked, so a typo in a limit() call fails at
compile time.
Email Verification Codes
A 6-digit, single-use, hashed, rate-limited email code that clears a sensitive-action step-up for any account type — the universal verification fallback.
Audit Log
A minimal, first-party audit log for application-level events. It is a starter baseline you can keep, extend, or delete — not a compliance product. A simple time-based retention cron bounds table growth, but there is no legal hold, immutable storage, or SIEM export.