Email OTP login has no attempt limit: useVerificationToken doesn't invalidate the code on a wrong guess (brute-forceable account takeover)
Los mantenedores suelen responder en 1 día
@addyCooks ya está trabajando en esto.
Desde el 5/9/2026.
Evaluación
- Dificultad
- 3/5
- Tiempo estimado
- 1-2 días
- Aptitud para principiantes
- 78/100
- Tipo de issue
- Error
- Claridad
- Bien especificado
- Estado de actividad
- Activo
- Stack tecnológico
- nextjs, typescript
- Área
- api, authentication, database
Línea de trabajo
Comienza en packages/database/auth/drizzle-adapter.ts, en useVerificationToken, y compara después su gestión de discrepancias con apps/web/app/api/mobile/[...route]/route.ts:543-548. Revisa el punto de entrada del callback de correo electrónico y las comprobaciones de auth existentes antes de validar el comportamiento. Se considera terminado cuando un código incorrecto invalida la fila de verificación y no se puede volver a intentar, mientras que la verificación correcta sigue funcionando.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Summary
useVerificationToken in packages/database/auth/drizzle-adapter.ts (lines 512-538) only
deletes the verification row on a successful match. A wrong 6-digit code guess finds no row
(lookup is by exact token value), logs a warning, and returns null the real code is left
untouched. It can be guessed without limit for its entire TTL (10 minutes as of #2068).
This is a logic bug, not just a missing rate limit: the mobile login path already does this
correctly. apps/web/app/api/mobile/[...route]/route.ts:543-548 deletes the row on a token
mismatch before returning "invalid", burning the code on the first wrong attempt. The web
(NextAuth) path has no equivalent.
Repro
POST /api/auth/signin/emailfor a target address.- Fire guesses at
GET /api/auth/callback/email?email=<target>&token=<6-digit>. - Codes come from
crypto.randomInt(100000, 1000000)(900,000 possible values,
auth-options.ts:138). At ~100 req/s, ~6.7% success chance within the 10-minute window;
sustained guessing lands a hit in a few hours.
Why this isn't closed by existing rate-limit ids
RATE_LIMIT_IDS.AUTH_OTP_VERIFY / AUTH_OTP_SEND (apps/web/lib/rate-limit.ts:83-85) are
declared but have zero call sites (see #2039, PR #1924 open and stale since June, PR #2040
closed without wiring these two). Even once wired, isRateLimited is backed by Vercel Firewall
and fails open without a matching dashboard rule, so it provides no protection on self-hosted
deployments regardless. The durable, hosting-agnostic fix has to live in
useVerificationToken itself.
Suggested fix
Delete (or otherwise invalidate) the verification row on a failed match too, not only on
success mirroring the mobile implementation. Wiring AUTH_OTP_VERIFY/AUTH_OTP_SEND as
defense-in-depth on Vercel deployments is worth doing separately, but shouldn't be treated as
the fix on its own since it doesn't cover self-hosted instances.
Related
- #2039 (open), broader unwired-rate-limit-ids issue, includes these two ids but not this root cause
- #1924 (open, stale), Firewall-based rate limiting, never merged
- #2068 (merged), fixed the 24h→10min window, explicitly left the attempt-counter fix as follow-up
- #2040 (closed), deliberately punted on wiring these two ids
- Lenguaje dominante
- Rust
- Estrellas
- 23k
- Forks
- 2k
- Merge medio
- 13 h 2 min
- PR fusionados (30 d)
- 70
Preparar el entorno
- Incluye un Dockerfile o un archivo de Docker Compose
- Sin plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de CapSoftware/Cap
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
CapSoftware/Cap#2384 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Dashboard pagination causes full page reloads on self-hosted CapPosiblemente ocupada @Dewin la tomó hace 18 días. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 88/100
CapSoftware/Cap#2305 · 2 comentarios ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
CapSoftware/Cap#1714 · 3 comentarios ·
Los mantenedores suelen responder en 1 día
-
Dificultad 5/5 Más de una semana Aptitud para principiantes 25/100
CapSoftware/Cap#2409 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
bug
Dificultad 4/5 3-5 días Aptitud para principiantes 64/100
CapSoftware/Cap#2408 · 1 comentario ·
Los mantenedores suelen responder en 1 día
Todos los issues de CapSoftware/Cap
Issues similares
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 74/100
-
review-drift
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
oxidecomputer/hansei#14 ·
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
rubys/roundhouse#444 ·
Los mantenedores suelen responder en 1 día
-
Published hardy-bpa-server image is built without the file-cla featurePosiblemente ocupada @EmbryoSpace la tomó hoy. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 85/100
ricktaylor/hardy#755 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 62/100
semaphoreci/docker-images#46 · 1 comentario ·