feat(auth): add per-IP rate limiting to signup, signin, and password reset#4527
feat(auth): add per-IP rate limiting to signup, signin, and password reset#4527waleedlatif1 wants to merge 1 commit intostagingfrom
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryMedium Risk Overview Configures Better Auth IP detection to trust Reviewed by Cursor Bugbot for commit 91e56a0. Configure here. |
Greptile SummaryThis PR adds per-IP rate limiting to the sign-up, sign-in, and password-reset endpoints in better-auth, and configures
Confidence Score: 3/5The change adds meaningful protection against the observed bot wave but introduces a spoofing vector that could let a determined attacker bypass the rate limit entirely on direct-to-ALB traffic. The apps/sim/lib/auth/auth.ts — specifically the
|
| Filename | Overview |
|---|---|
| apps/sim/lib/auth/auth.ts | Adds per-IP rate limiting for signup, signin, and password-reset endpoints, and configures cf-connecting-ip as the preferred IP header — but including x-forwarded-for as a fallback introduces a spoofing vector on direct-to-ALB traffic; in-memory storage also means limits are per-task, not cluster-wide. |
Reviews (1): Last reviewed commit: "feat(auth): add per-IP rate limiting to ..." | Re-trigger Greptile
| }, | ||
| advanced: { | ||
| ipAddress: { | ||
| ipAddressHeaders: ['cf-connecting-ip', 'x-forwarded-for'], |
There was a problem hiding this comment.
x-forwarded-for fallback enables rate-limit bypass
Including x-forwarded-for as a fallback header is exploitable when traffic reaches the ALB without going through Cloudflare. AWS ALB appends (but does not strip) client-supplied X-Forwarded-For values, so an attacker who reaches the ALB directly can send X-Forwarded-For: 1.1.1.1 and the ALB produces X-Forwarded-For: 1.1.1.1, <real-client-ip>. Better-auth reads the leftmost value, treating 1.1.1.1 as the client IP — a different spoofed address for every request, defeating the limit entirely. If cf-connecting-ip is always present when behind Cloudflare, the fallback only matters for requests that bypass Cloudflare, which are precisely the ones most at risk of IP-header forgery.
| rateLimit: { | ||
| enabled: true, | ||
| customRules: { | ||
| '/sign-up/email': { window: 600, max: 3 }, | ||
| '/sign-in/email': { window: 60, max: 10 }, | ||
| '/forget-password': { window: 600, max: 3 }, | ||
| }, | ||
| }, |
There was a problem hiding this comment.
In-memory rate limit state is per-ECS-task, not shared
The rate limiter defaults to in-memory storage, so each ECS task maintains its own counters. With N tasks running, any IP effectively gets max * N attempts before being blocked — e.g., 3 signups × 5 tasks = 15 signups unchecked cluster-wide. The PR description already flags this as a known follow-up, but it is worth noting explicitly: against a coordinated signup bot that distributes requests across tasks, the limit provides weaker-than-stated protection until storage: 'database' (or Redis) is added.
Summary
rateLimit.customRulesto better-auth: 3 signups per IP per 10 min, 10 sign-ins per IP per min, 3 password resets per IP per 10 minadvanced.ipAddress.ipAddressHeaderswithcf-connecting-ipso the limiter sees the real client IP behind Cloudflare (not the ALB)better-auth@1.3.12type definitions:rateLimit.customRules,advanced.ipAddress.ipAddressHeaders, and the three endpoint paths (/sign-up/email,/sign-in/email,/forget-password) are all present in the installed versionType of Change
Testing
Tested manually — verified the diff is scoped to
apps/sim/lib/auth/auth.tsonly. Rate limit defaults to in-memory storage (per-ECS-task); follow-up to considerstorage: 'database'for shared-state enforcement across pods if needed.Checklist