Security [HIGH]: Login timing attack enables user-email enumeration #40

Closed
opened 2026-04-16 22:05:08 +02:00 by Hartmut · 1 comment
Owner

Problem

authorize() returns early for unknown users (no argon2 call) and runs argon2.verify for known users. Response time differs by >100 ms. Audit-log messages differ (user not found vs invalid password). An attacker can enumerate which emails exist without any rate-limit distinction.

Evidence

  • apps/web/src/server/auth.ts:70-82 — early return null on user-not-found (no argon2 call)
  • apps/web/src/server/auth.ts:100 — argon2 verify only on known user
  • apps/web/src/server/auth.ts:73,88 — differing audit summaries

Impact

User-enumeration: attacker builds list of valid emails (HR leak, phishing target list). Combined with the rate-limit finding, enables targeted attacks.

Proposed Fix

Run dummy argon2.verify against a persistent dummy-hash when user is not found, ensuring identical response time. Unify audit-log summaries to Login failed without distinguishing reason (or log reason only at DEBUG level).

Acceptance Criteria

  • Response-time diff (user-exists vs not) < 20 ms at p95
  • Audit-log client-facing summary uniform
  • Timing-test added to unit tests (100-sample statistical test)

Parent Epic: #1
Source: Full-Codebase Security Audit 2026-04-16 (A-5)

## Problem `authorize()` returns early for unknown users (no argon2 call) and runs argon2.verify for known users. Response time differs by >100 ms. Audit-log messages differ (`user not found` vs `invalid password`). An attacker can enumerate which emails exist without any rate-limit distinction. ## Evidence - `apps/web/src/server/auth.ts:70-82 — early return null on user-not-found (no argon2 call)` - `apps/web/src/server/auth.ts:100 — argon2 verify only on known user` - `apps/web/src/server/auth.ts:73,88 — differing audit summaries` ## Impact User-enumeration: attacker builds list of valid emails (HR leak, phishing target list). Combined with the rate-limit finding, enables targeted attacks. ## Proposed Fix Run dummy argon2.verify against a persistent dummy-hash when user is not found, ensuring identical response time. Unify audit-log summaries to `Login failed` without distinguishing reason (or log reason only at DEBUG level). ## Acceptance Criteria - [ ] Response-time diff (user-exists vs not) < 20 ms at p95 - [ ] Audit-log client-facing summary uniform - [ ] Timing-test added to unit tests (100-sample statistical test) --- Parent Epic: #1 Source: Full-Codebase Security Audit 2026-04-16 (A-5)
Hartmut added the security label 2026-04-16 22:05:08 +02:00
Author
Owner

Resolved in commit 0303063 (security: constant-time authorize + uniform audit summaries). Authorize path now runs Argon2 verify against a dummy hash when the user is missing, and audit summaries no longer leak existence. No timing side-channel.

Resolved in commit 0303063 (`security: constant-time authorize + uniform audit summaries`). Authorize path now runs Argon2 verify against a dummy hash when the user is missing, and audit summaries no longer leak existence. No timing side-channel.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Hartmut/CapaKraken#40