Area: Account (re-run) (audit p1r) · Surface: POST /account/email/finalize (AccountController@finalizeEmailChange) · Dimension: security · Severity: minor
Between starting an email change and completing the multi-step code verification (a 30-minute window), the target email can be claimed by another account (e.g. that account changes its own email to this value, or a new signup). At finalize, the controller does not re-verify uniqueness and issues a bare UPDATE that violates the unique index, producing a raw 500 error rather than a graceful 'that email is now in use' message. Time-of-check/time-of-use gap (OWASP A04:2021 Insecure Design). Low security impact because the DB constraint prevents actual duplication, but it is a poor failure mode on a security-sensitive flow.
Evidence
Uniqueness is checked only at request time: AccountController.php:903-911 (`SELECT id FROM users WHERE email = :email AND id != :id` in changeEmail). finalizeEmailChange (AccountController.php:1044-1052) blindly runs `UPDATE users SET email = :email ... WHERE id = :id` with no re-check. The users table has UNIQUE KEY `uq_users_email` (schema.sql:7483), so if the target email was claimed by another account between the start and finalize of the flow, the UPDATE throws a PDOException. There is no try/catch here, so it bubbles to the global handler as a 500.
Suggested fix. In finalizeEmailChange, re-run the `email = :email AND id != :id` uniqueness check immediately before the UPDATE and flash a clean error if taken; additionally wrap the UPDATE in a try/catch for the duplicate-key PDOException so the user never sees a 500.
Filed by the automated tenant-app audit and adversarially evidence-verified. Status: verified. Open — not yet actioned.
Patrick Bass
@mobieus