OTP send hardening: layered rate limits (per-address cap, per-IP) #21

Open
opened 2026-06-11 08:02:46 +00:00 by ben.stull · 0 comments
Owner

Context

The OTP email-send surface (SD-0001 §5.2 / §6.6) currently has exactly one anti-abuse
control: the per-address 60s resend cooldown (INV-3). That stops accidental double-sends
but nothing else — it still allows ~1,440 emails/day to one victim address (mail bombing)
and unlimited spraying across many addresses from one client.

Wanted: layered controls (industry-standard stack)

  1. Per-address send cap — e.g. ≤5–10 sends per address per hour with escalating
    backoff; protects a victim inbox from sustained bombing.
  2. Per-IP / per-session rate limit across all addresses — stops one client spraying
    many addresses. Likely middleware/edge enforcement, not domain-layer.
  3. Escalation-only friction (CAPTCHA / proof-of-work) after abuse signals — never on
    the happy path; passwordless signup lives on first-attempt friction.

Keep the anti-enumeration property (§6.6 uniform responses) intact, and keep the honest
retry property (INV-9: failed delivery doesn't burn the cooldown).

Notes

  • Needs design thought on where limits live (domain vs middleware vs reverse proxy) and
    on honest UX copy for capped users — small but real surface, deserves its own plan.
  • Sibling of the frontend cooldown-lockout bug (filed separately); this is the backend
    hardening half.

Source

  • backend/app/domains/accounts/service.py::request_code
  • SD-0001 §5.2, §6.6; INV-3, INV-9
## Context The OTP email-send surface (SD-0001 §5.2 / §6.6) currently has exactly one anti-abuse control: the per-address 60s resend cooldown (INV-3). That stops accidental double-sends but nothing else — it still allows ~1,440 emails/day to one victim address (mail bombing) and unlimited spraying across many addresses from one client. ## Wanted: layered controls (industry-standard stack) 1. **Per-address send cap** — e.g. ≤5–10 sends per address per hour with escalating backoff; protects a victim inbox from sustained bombing. 2. **Per-IP / per-session rate limit** across *all* addresses — stops one client spraying many addresses. Likely middleware/edge enforcement, not domain-layer. 3. **Escalation-only friction** (CAPTCHA / proof-of-work) after abuse signals — never on the happy path; passwordless signup lives on first-attempt friction. Keep the anti-enumeration property (§6.6 uniform responses) intact, and keep the honest retry property (INV-9: failed delivery doesn't burn the cooldown). ## Notes - Needs design thought on where limits live (domain vs middleware vs reverse proxy) and on honest UX copy for capped users — small but real surface, deserves its own plan. - Sibling of the frontend cooldown-lockout bug (filed separately); this is the backend hardening half. ## Source - `backend/app/domains/accounts/service.py::request_code` - SD-0001 §5.2, §6.6; INV-3, INV-9
ben.stull added the type/storypriority/P2 labels 2026-06-11 08:02:46 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: wiggleverse/wiggleverse-ecomm#21