H6.11.4recovery account enumeration preventiondesignresearch

Recovery must not reveal whether an account exists to an unverified party

Aliases: user enumeration · forgot-password oracle · email registered

What it is

A signed-out person types an email or phone into the recovery box. If the UI answers “no such account” versus “we sent a code,” it has told them whether that identifier is registered. Not revealing existence means an unverified party always gets the same class of response (“if an account exists for this email, we sent mail”); the real difference appears only in the inbox of someone who holds that address. This entry is the enumeration surface of recovery. Listing your own bindings in signed-in settings is not enumeration. Timing differences on a wrong password at sign-in are also enumeration, but that is sign-in, not recovery.

Why it happens

Existence is an attacker’s directory: it enables targeted phishing, credential stuffing, or seeing which public figure uses the product. Recovery APIs naturally look up an identifier and most easily turn existence into an error code. Neutral wording still leaks if timing, whether mail is actually sent, or HTTP status differs. Defense makes lookup, sending, and timing constant: nonexistent identifiers take the same duration and the same front-end sentence; mail goes out only when the identifier is bound and the channel works, but the front end does not report that branch. Channel holders learn “there is an account” from the mail; non-holders learn nothing. Rate limits are still required, or enumeration moves to “wait for mail at every address.”

Studying it

Hit the recovery API with known-existing and known-missing identifiers; compare body, status, latency distribution, and front-end copy.

Independent variables: paired copy differences, constant-time handling, pretending success for missing identifiers. Dependent variables: accuracy of classifying existence from the response alone, whether mailbox holders can still finish recovery, share who never check the inbox because the UI did not say “sent.”

Do not judge only string equality in the lab—plot latency and status codes. Neutral copy will make some people think nothing was sent; the fix is to tell them to check spam, not to revert to “no such account.” Enumeration on the sign-in API must be measured separately; fixing recovery does not fix sign-in.

Where it stops holding

An enterprise IdP that must refuse to provision an unknown mailbox leaks existence as part of provisioning, which is not public self-serve recovery. A signed-in user changing their own email must be told “this new address is taken”; that is a conflict notice to an already-verified party. A public profile already asserts existence, so hiding it on recovery is limited in value, but the recovery box still should not extra-confirm. Human support may confirm existence after the agent has passed their own authentication—not an unverified visitor on the web.

Applying it

  • Recovery submit returns the same sentence and status for every well-formed identifier, e.g. “If an account exists, reset instructions were sent.”
  • The backend takes similar time for exist and not-exist; mail is sent only if it exists; the front end has no branch.
  • Mail subject and preview must not say “you have no account” before open; mail is sent only when there is an account.
  • Verify: a script submitting recovery for a batch of existing and missing addresses cannot classify better than chance from the response alone. A person who holds a test mailbox should still receive the link and finish reset. Latency distributions for the two groups should overlap, not bimodal.

Related

  • Within the group: H6.11.1 Recovery must not leak answers easier to guess than the password · H6.11.2 Reset links must expire and be single-use · H6.11.3 After reset, all existing sessions must be invalidated
  • Adjacent: H6.02 Third-party login · H6.01 Registration friction
  • Search terms: user enumeration · account oracle · forgot password

Cards in the same group

Quick Actions

Share

Share this page

ios_share

https://hci.top/en/handbook/H6.11.4