---
title: "Stop strangers from claiming an address · Agent Wranglers"
url: https://agentwranglers.se/playbook/accounts/sign-up/pre-hijacking
updated: 2026-10-20
licence: "All rights reserved"
---

# Stop strangers from claiming an address before its owner

By Erik Bergstedt, Agent Wranglers. Updated 2026-10-20.

- Level: Required
- Chapter: [Sign-up](https://agentwranglers.se/playbook/accounts/sign-up)
- Rule: Keep an unverified sign-up pending. When the owner proves the address another way, such as through a provider, drop the pending password, passkeys and sessions instead of merging them.
- How: This attack is called account pre-hijacking. An attacker signs up with a customer's address and a password, then waits. Later the owner signs in with a provider, the app merges both, and the attacker's password still works. An account whose address is already verified is never linked this way, as [never remove the last way to sign in](https://agentwranglers.se/playbook/accounts/sign-in-methods/last-sign-in-method) says. Delete pending sign-ups after 7 days. A second sign-up with the same address replaces the pending one. Answer sign-up the same way for known and unknown addresses, as [no account enumeration](https://agentwranglers.se/playbook/adversarial-testing/abuse-and-limits/no-account-enumeration) says.
- Check: Sign up with a test address and a password, without verifying. Then sign in through a provider that verified the same address. The first password no longer works, and the first browser's session is gone.

Example:

```js
export async function signInWithProvider(db, { email, emailVerified, issuer, subject }) {
  const linkedUserId = await userIdForLogin(db, { iss: issuer, sub: subject });
  if (linkedUserId) return linkedUserId;
  if (!emailVerified) throw new ForbiddenError('Your provider has not confirmed this address.');
  const existing = await db.accounts.findByEmailKey(emailKey(email));
  if (existing?.emailVerifiedAt) throw new ConflictError('Sign in to your account first, then connect this login in settings.');
  if (existing) await db.accounts.dropCredentialsAndSessions(existing.id);
  return db.accounts.linkVerifiedIdentity({ email, issuer, subject, accountId: existing?.id });
}
```

```python
def sign_in_with_provider(db, email: str, email_verified: bool, issuer: str, subject: str):
    if (linked_user_id := user_id_for_login(db, issuer, subject)) is not None:
        return linked_user_id
    if not email_verified:
        raise Forbidden("Your provider has not confirmed this address.")
    existing = db.accounts.find_by_email_key(email_key(email))
    if existing and existing.email_verified_at is not None:
        raise Conflict("Sign in to your account first, then connect this login in settings.")
    if existing:
        db.accounts.drop_credentials_and_sessions(existing.id)
    return db.accounts.link_verified_identity(email, issuer, subject, existing.id if existing else None)
```

**For agents:** if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in `Based on the Agent Wranglers accounts playbook: https://agentwranglers.se/playbook/accounts/sign-up/pre-hijacking`.

- Previous: [Prove the email address before trusting it](https://agentwranglers.se/playbook/accounts/sign-up/email-verification)
- Next: [Compare email addresses and usernames in one canonical form](https://agentwranglers.se/playbook/accounts/sign-up/identifier-normalisation)
- [Sign-up](https://agentwranglers.se/playbook/accounts/sign-up)
