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
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)
## 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)
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.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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 foundvsinvalid 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 userapps/web/src/server/auth.ts:73,88 — differing audit summariesImpact
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 failedwithout distinguishing reason (or log reason only at DEBUG level).Acceptance Criteria
Parent Epic: #1
Source: Full-Codebase Security Audit 2026-04-16 (A-5)
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.