Repository navigation
[Security]: Use a distributed rate limiter for portal login attempts #24
Description
Activity
Implementation started on branch fix/lab-correctness-batch. The design is being checked against the current Vercel/Cloudflare storage architecture so the limiter is genuinely shared across serverless instances, not another process-local counter.
Implementation update: a dedicated production D1 database has been created and migration 0001 applied. The exact atomic UPSERT/RETURNING contract was exercised in Cloudflare D1 Console against a temporary HMAC-shaped bucket and the probe row was deleted afterward. Local web tests (120/120), TypeScript, and production build pass. Independent review identified two hardening items now being addressed before PR/deploy: do not advance the account bucket after a client is already blocked, and bound expired bucket-row retention. Production token/HMAC environment provisioning and concurrent REST acceptance remain pending the required action-time secret authorization.
Implementation is ready in PR #26, but must not be merged/deployed yet because production intentionally fails closed until its three Vercel variables are installed.
What changed:
- Cloudflare D1 atomic fixed-window client gate runs before KV/admin lookup (10 attempts / 15 min)
- configured-admin or bounded decoy account gate runs before password verification (30 / 15 min)
- successful and failed attempts both consume quota
- bucket keys are HMAC-only; logs and errors are generic
- malformed/absent forwarded IP uses one bounded unknown bucket
- expired rows are pruned by schema triggers
- D1 timeout/malformed/error paths fail closed with 503; local tests use deterministic memory storage
The production D1 schema, index, trigger and exact UPSERT/RETURNING behavior have been created and live-probed. Portal verification passes 124 tests, TypeScript and production build.
Remaining operational gate: provision
CLOUDFLARE_D1_DATABASE_ID, a least-privilegeCLOUDFLARE_D1_API_TOKEN, andLOGIN_RATE_LIMIT_KEY_SECRETin Vercel, deploy, then smoke-test 429/503/login behavior.Production activation completed after PR #26 merge.
- Account-owned Cloudflare token is limited to D1 Write.
- All three D1 variables are Sensitive and enabled for Production + Preview.
- Exact PR Preview passed live admission smoke: attempts 1-10 returned generic 401 and attempt 11 returned 429.
- Production deployment is Ready; root is 200, session reports configured=true, and production uses the same D1 bucket successfully.
- The temporary Preview-build override was restored to Only build production.
Closing because implementation, deployment, and smoke acceptance are complete.
Problem
The portal login limiter is process-local, so counters reset across serverless instances and cold starts.
Scope
Acceptance