Email OTP login has no attempt limit: useVerificationToken doesn't invalidate the code on a wrong guess (brute-forceable account takeover)
まだ誰も着手していません。
評価
- 難易度
- 3/5
- 見積もり時間
- 1〜2日
- 初心者へのやさしさ
- 78/100
- issue の種類
- バグ
- 明瞭さ
- 明確に書かれている
- 活発さ
- 活発
- 技術スタック
- nextjs, typescript
- 領域
- api, authentication, database
調査の方向性
packages/database/auth/drizzle-adapter.ts の useVerificationToken から始め、apps/web/app/api/mobile/[...route]/route.ts:543-548 と不一致時の処理を比較してください。動作を検証する前に、メール callback のエントリーポイントと既存の auth チェックを確認してください。誤ったコードによって検証行が無効化され、再試行できなくなり、正しい検証は引き続き機能する状態になれば完了です。
索引モデルが issue の本文から書いたものです。
説明
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
- 主要言語
- Rust
- スター
- 22.5k
- フォーク
- 1.9k
- 平均マージ
- 7時間 43分
- マージ済み PR(30日)
- 80
コントリビューションガイド
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
CapSoftware/Cap のほかの issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 88/100
CapSoftware/Cap#2305 · コメント 2 件 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
CapSoftware/Cap#1714 · コメント 3 件 ·
-
難易度 4/5 3〜5日 初心者へのやさしさ 45/100
CapSoftware/Cap#2340 · コメント 1 件 ·
-
難易度 4/5 3〜5日 初心者へのやさしさ 45/100
CapSoftware/Cap#2339 · コメント 1 件 ·
-
難易度 3/5 1〜2日 初心者へのやさしさ 58/100
CapSoftware/Cap#2329 · コメント 2 件 ·
CapSoftware/Cap の issue をすべて見る
似ている issue
-
bug github_actions
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
registrystack/registry-stack#1393 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
longbridge/gpui-kit#3223 ·
-
bug engine
難易度 2/5 1〜3時間 初心者へのやさしさ 65/100
rocky-data/rocky#2181 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 70/100
oasisprotocol/oasis-sdk#2523 ·
-
[indexer] [QA] Add a focused test for the new NonRetryableError / assertSocketAlive() behavior. オープンbot:ai-assisted component:indexer QA-roadmap status:untriaged
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
midnightntwrk/midnight-indexer#1557 ·