Forums Bug Reports Thread

Verification confirm-email: token is not bound to the session user, and lookup failure silently advances any logged-in user to the selfie step for their own verification

Patrick Bass · Jun 6 · 17 · 1 Locked
[Minor] [Normal Priority] [Bug Fixed] [Always Reproduces]
🚀 OP Jun 6, 2026 9:35pm

Area: Account (re-run) (audit p1r) · Surface: GET /account/verified/confirm-email (VerificationController@confirmEmail) · Dimension: security · Severity: minor

The email-confirmation token is treated as proof of email ownership but is never tied to the authenticated session user, and on any lookup failure the controller still advances whoever is logged in to the selfie-upload step of their own verification. A 64-char string that fails the API lookup is enough to push the current user into completing verification. Combined with the GET being state-changing (it flips email_verified_at), this is a weak binding between the token, the email recipient, and the session — OWASP A01:2021 / A07:2021. Impact is limited because the platform-admin side ultimately reviews submissions, but the confirm step should bind token→user.

Evidence

VerificationController.php:247-301. The token from the email link is looked up via PlatformAdminClient (`/api/verification/by-token?token=...`) but the returned verification is never checked to belong to the current session user before marking it email-verified (lines 262-270: `PlatformAdminClient::doPost('/api/verification/update', ['id' => $vid, 'email_verified_at' => ...])`). On the failure branch (vid <= 0) it calls `$this->requireAuth()` and forwards the user to `/account/verified/selfie?vid=...` for THEIR current verification regardless of whether the clicked token was valid (lines 271-284), with only a length check (line 250 `strlen($token) !== 64`) gating entry.

Suggested fix. After resolving the token, verify the returned verification's user_id (zitadel_sub) matches the authenticated user before marking email_verified_at; on lookup failure do NOT auto-advance to the selfie step — show an explicit 'invalid or expired link' error and require the user to restart from /account/verified.

Filed by the automated tenant-app audit and adversarially evidence-verified. Status: verified. Open — not yet actioned.


Patrick Bass
@mobieus

🚀 Jun 7, 2026 5:49am

Resolved — fixed and deployed. Commit ea9f0311e960, shipped dev-first then to all tenants on 2026-06-06.

confirmEmail now resolves the authenticated owner up front and uses hash_equals to confirm the by-token verification's user_id (zitadel_sub) matches the session user before stamping email_verified_at; on token non-resolution, owner mismatch, or any lookup exception it now shows an explicit 'invalid or expired link' error and redirects to /account/verified instead of auto-advancing to the selfie step.

Status: fixed. Thread closed and locked.


Patrick Bass
@mobieus

Log in or register to reply to this thread.