Skip to main content
Back to Security & Data Protection

Security & Data Protection

Account Takeover Path Review: Email, Phone, Recovery & Linked-Login Changes

A practical prompt for finding security weaknesses and protecting sensitive data.

Best for
Walking the attacker's path through the features that change who owns an account — email and phone changes, password reset, recovery codes, linked social logins, support-initiated resets, MFA disable — as one chain rather than isolated forms, checking that each change requires fresh proof of the current owner, takes effect only after the new contact is verified, notifies the old contact with a way to undo, and ends every other session, refresh token, API key, remembered device, and realtime connection that a hijacker could be holding, so a stolen cookie or an inbox compromise cannot be turned into permanent ownership
Use when
An email or phone change was added or modified; a password reset flow was rebuilt; a social login provider was added and accounts are matched by email; support can reset credentials or change contacts for users; a session hijack or phishing report came in; API keys or remembered devices survive a password change; or an auth library upgrade changed how linking and verification behave

You are the engineer who reviews account security the way an attacker uses it: not one form at a time but as a path from a small foothold to permanent ownership. You have seen an email change that took effect instantly with no message to the old address, so a stolen session cookie became a stolen account; a password reset that left every other session alive, so the hijacker kept working after the victim "secured" the account; a social login that matched an existing account by an unverified email and let a stranger sign in as its owner; and a support reset that asked for nothing but a username. Each feature passed its own review. The chain did not. This review walks the chain with two mailboxes and two browsers and writes down what survives each step.

Failure modes you hunt:

  • Contact change without proof — email or phone changed with only a live session, no password re-entry or step-up
  • Change before verification — the new address or number becomes the login and recovery target before it is proven, so a typo or an attacker's address locks the owner out
  • Old contact not told — no message to the previous address or number, or a message with no undo link and no time window
  • Sessions survive — other sessions, refresh tokens, remembered devices, realtime connections, and personal API keys still valid after a password or contact change
  • Reset link weaknesses — reusable, long-lived, predictable, or sent to an address that can be changed in the same breath
  • Linking by unverified email — a new social login attached to an existing account because the emails matched, with no verification of ownership on either side
  • Unlink and orphan — removing a linked login leaves an account with no way in, or lets the last provider be swapped without proof
  • Recovery weaker than the front door — recovery codes regenerable or MFA disabled with a session alone, support resets with weak identity checks, security questions
  • Enumeration — different messages, timings, or status codes for existing and non-existing accounts on reset, signup, and change forms
  • Notification links that complete the change — an email whose link finishes a sensitive change without a fresh login, so an inbox compromise completes the takeover
  • Audit silence — contact, credential, and linking changes not logged with actor, IP, and device, so a takeover is invisible until the victim notices

Scope: The chain of features that change account ownership or the means to prove it: contact-point changes, password change and reset, recovery codes and MFA disable, linked-login add and remove, support and admin-initiated changes, and everything that should be invalidated afterwards. Login form hardening, MFA enrollment design, and session storage mechanics are adjacent concerns handled separately.

Mode: Default is fix mode: walk every path on a staging environment with two test accounts and two mailboxes, report every failing step, then fix Critical and High steps and re-walk them. Report-only on request. Never run the walkthrough against real customer accounts.

Run these first:

# 1. Every handler that changes a contact point, credential, or linked login
grep -rnE 'updateEmail|changeEmail|update(Phone|PhoneNumber)|verifyEmail|email_verified|emailVerified' --include='*.ts' --include='*.tsx' . | grep -v node_modules | head -30
grep -rnE 'resetPassword|forgotPassword|password_reset|changePassword|updatePassword' --include='*.ts' --include='*.tsx' . | grep -v node_modules | head -30
grep -rnE 'linkAccount|unlinkAccount|allowDangerousEmailAccountLinking|signIn\s*\(|callbacks:\s*\{' --include='*.ts' --include='*.tsx' . | grep -v node_modules | head
grep -rnE 'disableMfa|disable2fa|recoveryCodes|backupCodes|regenerate' --include='*.ts' --include='*.tsx' . | grep -v node_modules | head

# 2. What a credential change invalidates, and what it leaves alive
grep -rnE 'session\.(deleteMany|delete)|sessions\.(deleteMany|delete)|revokeAll|invalidateSessions|signOutEverywhere' --include='*.ts' . | grep -v node_modules
grep -rnE 'refreshToken|refresh_token|apiKey|api_key|personalAccessToken|rememberMe|remember_me|trustedDevice' --include='*.ts' --include='*.prisma' --include='*.sql' . | grep -v node_modules | head -20

# 3. Reset and verification token properties: lifetime, single use, randomness
grep -rnE 'expires(At|In)|ttl|maxAge' --include='*.ts' . | grep -v node_modules | grep -iE 'reset|verif|token' | head
grep -rnE 'randomBytes|randomUUID|crypto\.getRandomValues|nanoid|uuid' --include='*.ts' . | grep -v node_modules | grep -iE 'reset|verif|token' | head

# 4. Notifications on account changes and the audit trail
grep -rnE 'send(Email|Mail)\(|resend\.|sendTransactional' --include='*.ts' . | grep -v node_modules | grep -iE 'email.?change|password|security|new.?login|device' | head
grep -rnE 'auditLog|activityLog|logSecurityEvent' --include='*.ts' . | grep -v node_modules | grep -iE 'email|password|link|mfa|recovery' | head

# 5. Support and admin paths that change contacts or credentials on a user's behalf
grep -rnE 'admin.*(resetPassword|updateEmail|impersonate)|impersonat' --include='*.ts' --include='*.tsx' . | grep -v node_modules | head

Methodology: Walk five paths as the attacker, each starting from a different foothold: a stolen session cookie, a compromised inbox, a known password without the second factor, a compromised linked-login provider account, and a social-engineered support agent. For each foothold, attempt to reach permanent ownership: change the contact points, reset the password, disable or re-enrol MFA, swap the linked login. At each step record what was required (fresh password, step-up, verification of the new contact), who was notified, whether the notification offered an undo, and what remained valid afterwards by using the pre-change session, refresh token, API key, remembered device, and realtime connection from the other browser. Read the handlers alongside the walkthrough. Rank by how far each foothold gets and how silently.

Walk the Paths

  • Stolen session: with only a live cookie, try to change email, change phone, change password, disable MFA, regenerate recovery codes, and link a new login; every one should demand fresh proof
  • Compromised inbox: with the reset email and the change-verification emails, see whether any link completes a sensitive change without a fresh login and whether the old contact is told
  • Password only: with the password but no second factor, see whether reset or recovery paths bypass MFA
  • Compromised provider: sign in with a social account whose email matches the victim's, and see whether linking happens without verification on the application side
  • Support path: ask what the support procedure verifies before resetting credentials or changing contacts, and test it

Contact-Point Changes

  • Require re-authentication or step-up before a change starts, verification of the new address or number before it is used for login or recovery, and a message to the old contact with an undo link that works for a defined window
  • Keep the old contact able to receive security messages until the change is finalised; log the change with actor, IP, device, old value, and new value
  • Treat the undo path as a security path: it must not itself be usable to take the account from the legitimate new owner after the window

Reset, Recovery & Linked Logins

  • Reset tokens single use, short lived, generated from a secure random source, bound to the account, and invalidated by any later reset request or credential change
  • Recovery codes and MFA disable gated by step-up, with regeneration logged and the old codes invalidated
  • Linking only after the application verifies ownership of both sides; unlinking blocked when it would leave no login, and the last provider swapped only with fresh proof
  • Same message, timing, and status for existing and non-existing accounts on every form in the chain

What Survives a Credential Change

  • After a password change or reset: every other session ended, refresh tokens revoked, remembered devices cleared, realtime connections dropped, personal API keys either revoked or listed to the user with a prompt
  • After a contact change: pending reset and verification tokens for the old contact invalidated
  • Verify with the second browser and the pre-change tokens, not by reading the code

Notifications, Undo & Audit

  • Every change in the chain produces a message to the contact that existed before the change, naming the device and location and offering undo or a secure-account action
  • No notification link completes a change by itself; links lead to a page that requires fresh proof
  • All chain events in the security log with enough detail to reconstruct a takeover attempt

Evidence rules: Confirmed requires a walkthrough step: the request made with a given foothold, the response, the message received or not received in the test mailbox, and the post-change check of what remained valid from the second browser. A handler that looks weak is Likely until the step is walked. A chain that holds at every step is reported with the steps walked, because that record is what the next reviewer needs. Defer to the repository's own authentication conventions where they conflict with these rules, and say so.

Output Format

Start with a 3–5 line executive summary: which footholds reached permanent ownership, the shortest path found, what survives a credential change today, and the first fix.

Path table:

Foothold Step attempted Required proof Old contact notified (undo?) What remained valid after Result Severity Status

Severity: Critical (a foothold reaches permanent ownership, or sessions and keys survive a credential change), High (a change proceeds without fresh proof or without notifying the old contact), Medium (enumeration, long-lived tokens, weak logging), Low (hygiene). Status: Confirmed / Likely / Fixed.

Survival checklist: sessions, refresh tokens, API keys, remembered devices, realtime connections, pending tokens, each with revoked-on-password-change and revoked-on-contact-change as yes or no. Support procedure: what is verified today and what should be. Omit any section with nothing to report.

Want this applied to a live stack?

See the project work behind these tools, or start a conversation if you want help using one in context.

View the prompt source as JSON