p1-signup-timing-equalization

readytype/backlogpriority/p1severity/mediumtopic/authtopic/enumeration

p1 · Signup timing equalization

TL;DR

Reduce the timing side channel on signup(): new-user vs existing-user paths do different DB and email work today, so response duration can still hint whether an email is registered despite identical JSON/UI.

Description

  • Problem: C3 fixed message and response shape ([[Projects/personal-finance-notion/backlog/done/p0-c3-signup-enumeration|C3 done]]). Branches in src/lib/actions/auth.ts (createUser + verify vs re-send verify vs account-exists-already) have different cost profiles.
  • Context: Login timing (H2) shipped in [[Projects/personal-finance-notion/backlog/done/p0-authjs-phase-d-pre-launch-hardening|Phase D]] (DUMMY_HASH in authorize()). Signup is a separate surface with the same audit theme.

Acceptance criteria

  • Document target: e.g. 20× new-email vs 20× existing-email signups within ±5ms median (or justify a looser bound in ADR/audit note)
  • Implement equalization strategy (pick one): fixed minimum delay before return; parallel no-op work on fast path; background email send after immediate success response
  • Do not regress enumeration-safe success shape or email delivery
  • Optional: Vitest or script timing check in CI (flaky-aware thresholds)

Implications

If skipped

  • Sophisticated attackers can still enumerate registered emails via signup latency even when copy and JSON match. Feeds targeted phishing and credential stuffing against known addresses on a finance app.

Why this priority

  • p1 — defense in depth after C3 fixed response text/shape; login H2 timing already shipped in Phase D. Attackers can still probe signup latency before this lands.

When shipped

  • C3 is complete for both content and timing per original audit verification.

Dependencies

  • Related: [[Projects/personal-finance-notion/backlog/done/p0-h2-login-timing|H2 login timing (done)]]

Links

  • App repo: src/lib/actions/auth.ts (signup)