Skip to content

What To Do After a Data Breach

💡
Before you start

Python 3 and a terminal. No real breach data is used or downloaded, and nothing connects to the internet. macOS and Linux include Python; on Windows install it from python.org with “Add python.exe to PATH” ticked, then check with python3 --version.

Do not put a real password of yours into these files. The examples exist to show how a stolen table behaves, not to test anything of yours. Every email address below uses the example.com domain, which is reserved for documentation and reaches nobody.

First, Calibrate

Breach notifications arrive often enough that many people have stopped reading them. That is understandable and slightly dangerous. The right response is neither panic nor indifference: it is a short, ordered checklist whose length depends entirely on what leaked.

The FBI's Internet Crime Complaint Center attributed roughly $1.3 billion in reported losses to personal data breaches in 2025 (FBI IC3 2025 Internet Crime Report, accessed 5 September 2026). Most of that harm does not come from the breach itself. It comes from what is done with the data afterwards -- credential stuffing, targeted phishing, and account recovery abuse.

💡
The severity ladder.

An email address and a marketing preference is a nuisance. A password, even hashed, is serious. A government identifier, bank details, or the answers to your security questions is severe -- because unlike a password, you cannot change your date of birth.

Find Out What Actually Leaked

  • Read the notification properly -- but go to the company's site by typing the address yourself. Fake breach notices are themselves a common phishing lure, and they arrive at exactly the moment you are primed to click
  • Check a reputable breach-notification service to see which of your addresses appear in which incidents, and sign up for future alerts
  • Find out how passwords were stored -- plaintext or a weak hash means assume cracked. A modern algorithm buys you time but not immunity
  • Note whether security-question answers leaked -- these are quietly among the worst, because the same answers are reused across every service you own
  • Watch for stealer-log entries specifically -- if your data appears because your own device was infected rather than a company being breached, the device must be cleaned first or everything you do next is wasted

The First Hour

1
Change the password on the breached service.

Use a unique password you have never used elsewhere. If you no longer need the account, close it properly rather than abandoning it.

2
Change it everywhere you reused it.

This is the step that matters most. Attackers take leaked pairs and try them across hundreds of sites automatically -- credential stuffing. Reuse is what turns one company's failure into your problem. Prioritise email, banking, and anything financial.

3
Revoke active sessions on the affected accounts.

Look for "sign out everywhere" in security settings. A password change does not always end sessions that are already open.

4
Turn on the strongest second factor available.

A passkey if offered, otherwise an authenticator app or hardware key. This is the moment you are motivated -- use it.

5
If card details leaked, replace the card.

Do not merely watch it. Ask the bank for a new number, and check any subscriptions that will need updating.

The First Day

  • Freeze your credit if a national identifier leaked -- in the US, place a freeze with all three bureaus. It is free, it stops new accounts being opened in your name, and it is far more effective than paid monitoring, which only tells you after the fact. Elsewhere, ask your national credit reference agencies for the equivalent protection
  • Change security-question answers anywhere you used the leaked facts -- and make the new answers fictional, stored in your password manager
  • Check the account's own recovery settings for a phone number, email, or forwarding rule you did not add
  • Review connected applications and revoke anything you do not recognise
  • Tell your bank if identity documents leaked, so they can flag the account for stricter verification

Expect the Second Wave

The most predictable consequence of a breach is not fraud on the breached account. It is a wave of highly convincing scams built from the leaked data, and they arrive precisely when you are expecting contact about a breach.

  • Phishing that quotes real details -- your address, a recent order, the last four digits of a card. These details prove only that the sender read the same leak everyone else did
  • Fake "breach support" calls claiming to be from the breached company or your bank, offering to secure your account. Hang up and call the number on your card
  • Fake compensation or settlement claims asking for bank details to pay you
  • Blackmail emails quoting an old password as proof of a non-existent hack. If the password is genuinely still in use anywhere, change it; otherwise delete the message
  • Recovery-service offers promising to erase your data from the internet for a fee
⚠️
Knowing your details is not proof of identity.

After a breach, the one thing you can be certain of is that criminals hold accurate information about you. Correct personal details in a message are now evidence of a leak, not evidence of legitimacy. Verify every unexpected contact on a number you already had.

The First Month

  • Read your statements line by line for two or three cycles -- small test charges precede large ones
  • Check your credit report for accounts you did not open
  • Move to a password manager if you have not -- reuse is the underlying vulnerability, and no amount of vigilance fixes it manually
  • Work through your reused passwords systematically -- most managers have an audit view that ranks them for you
  • Adopt passkeys where offered, starting with email
  • Reduce what you hand over next time -- an email alias per service means the next breach identifies itself and can be switched off in one click

What Not to Bother With

Breach anxiety is heavily monetised, and some of the standard advice is close to worthless.

  • Paid credit monitoring is rarely worth it -- a free freeze prevents the harm; monitoring only reports it afterwards
  • Do not change every password you own -- change reused ones and important ones. An exhausting sweep that you abandon halfway is worse than a focused one you finish
  • Deleting the account does not unpublish the data -- the copy is already out. Delete it if you want, but do the password work first
  • Ignore services promising to erase you from the internet -- broker opt-outs have modest value; total erasure is not a thing that exists

Work Out What a Breach Notice Actually Costs You, in Five Steps

A breach notification is written by lawyers to be accurate and reassuring at the same time, which makes it very hard to read for the only thing you need: what can somebody now do that they could not do last week? In the next twenty minutes you will translate the list of leaked fields into a list of capabilities, find out why one design decision at the breached company decides whether your password is now theirs, put the response steps in an order that works, and separate the measures that prevent fraud from the ones that merely announce it. Every line of output below came from running these files.

1
Translate the leaked fields into what they let somebody do

Go: open a terminal in a folder you can write to — cd ~/Desktop on macOS or Linux, cd %USERPROFILE%\Desktop on Windows.

Do: save this as exposed.py and run python3 exposed.py.

"""Read the breach notice as a list of capabilities, not a list of fields."""

NOTICE = {
    "email address":        "lets an attacker find your other accounts and target you",
    "password hash":        "may become your password -- depends entirely on the algorithm",
    "full name":            "passes identity checks that ask for it",
    "date of birth":        "passes identity checks; cannot be changed, ever",
    "postal address":       "passes identity checks; changeable but slowly",
    "phone number":         "SMS resets, and SIM-swap targeting",
    "security question answers": "unlocks recovery on OTHER sites that ask the same question",
    "last 4 card digits":   "makes a scam call sound authoritative",
}

CHANGEABLE = {"email address", "password hash", "phone number", "postal address"}

print("%-28s %s" % ("WHAT LEAKED", "WHAT IT LETS SOMEONE DO"))
print("-" * 92)
for field, effect in NOTICE.items():
    print("%-28s %s" % (field, effect))

print()
print("fields you can change  :", len(CHANGEABLE))
print("fields you cannot      :", len(NOTICE) - len(CHANGEABLE))
print("   ", ", ".join(sorted(set(NOTICE) - CHANGEABLE)))
print()
print("The permanent half is why 'just change your password' is incomplete")
print("advice. Your date of birth is now, and will always be, known.")

You should see: half the fields in a column you can do nothing about:

WHAT LEAKED                  WHAT IT LETS SOMEONE DO
--------------------------------------------------------------------------------------------
email address                lets an attacker find your other accounts and target you
password hash                may become your password -- depends entirely on the algorithm
full name                    passes identity checks that ask for it
date of birth                passes identity checks; cannot be changed, ever
postal address               passes identity checks; changeable but slowly
phone number                 SMS resets, and SIM-swap targeting
security question answers    unlocks recovery on OTHER sites that ask the same question
last 4 card digits           makes a scam call sound authoritative

fields you can change  : 4
fields you cannot      : 4
    date of birth, full name, last 4 card digits, security question answers

The permanent half is why 'just change your password' is incomplete
advice. Your date of birth is now, and will always be, known.

The split at the bottom is the part breach notices never state. Four of these you can change this afternoon. The other four are permanent facts about you: your date of birth will be the same next year, your name is unlikely to change, and the answers to your security questions are now known to whoever bought the dump — and those answers unlock recovery on other sites that ask the same questions.

That is why the response has to include something beyond changing a password. The permanent fields cannot be revoked, so the only remedy is to stop relying on them, which is what the last row of step 5 is about.

If not: the counts are derived from the two sets, so editing a field moves them correctly; if the second list is empty, a name in CHANGEABLE was misspelled and no longer matches its key in NOTICE.

2
Find out whether the password hash matters

Go: the same folder.

Do: save this as salt.py and run python3 salt.py.

"""Why one word in the database design decides how bad the breach is."""
import hashlib, secrets

USERS = ["ana", "ben", "cara", "dan", "eve"]
# Three of the five happened to choose the same very common password.
PASSWORDS = ["hunter2", "hunter2", "T7#qLm2v", "hunter2", "kx91-pw"]

print("WITHOUT A SALT")
unsalted = [(u, hashlib.sha256(p.encode()).hexdigest()) for u, p in zip(USERS, PASSWORDS)]
for user, h in unsalted:
    print("   %-6s %s" % (user, h[:40]))
groups = {}
for user, h in unsalted:
    groups.setdefault(h, []).append(user)
repeated = [users for users in groups.values() if len(users) > 1]
print("   accounts sharing a hash:", repeated)
print("   crack that ONE hash and you have:", len(repeated[0]), "accounts")

print()
print("WITH A PER-USER SALT")
salted = []
for user, p in zip(USERS, PASSWORDS):
    salt = secrets.token_hex(8)
    salted.append((user, hashlib.sha256((salt + p).encode()).hexdigest()))
for user, h in salted:
    print("   %-6s %s" % (user, h[:40]))
print("   accounts sharing a hash:", [])
print("   crack one hash and you have: 1 account")
print()
print("The salt is not secret and does not need to be. It exists so that")
print("identical passwords stop producing identical hashes -- which kills")
print("precomputed tables and stops one crack from unlocking many people.")

You should see: three users sharing one hash, then five users sharing none:

WITHOUT A SALT
   ana    f52fbd32b2b3b86ff88ef6c490628285f482af15
   ben    f52fbd32b2b3b86ff88ef6c490628285f482af15
   cara   ce21c9f16dd3f836803087010bb00e4b36e7d09f
   dan    f52fbd32b2b3b86ff88ef6c490628285f482af15
   eve    6f84389aa108a118f93a4ba82d0272d46f82948b
   accounts sharing a hash: [['ana', 'ben', 'dan']]
   crack that ONE hash and you have: 3 accounts

WITH A PER-USER SALT
   ana    cca272d9d8db1e7a7e04f3d95599cf6016d9789e
   ben    e271cca170c54151dc8a1fbc6cc2b5b80b33a595
   cara   f9b5e235c2cddd4f1b65cf3248691b65916673fa
   dan    cfdc7b272589c2256f8d27c25171ff220951d038
   eve    ca17fb15df2ff995962dea5b1da8a8d5a5022606
   accounts sharing a hash: []
   crack one hash and you have: 1 account

The salt is not secret and does not need to be. It exists so that
identical passwords stop producing identical hashes -- which kills
precomputed tables and stops one crack from unlocking many people.

The salted block will be different every time you run it, and that is the demonstration — the salt is random per user, so the same password produces a different hash on every run. The unsalted block is identical on your machine and on this page, which is exactly the property that makes it dangerous.

Without a salt, identical passwords produce identical hashes. An attacker sorts the stolen table, sees which hash appears most often, cracks that one, and immediately owns every account that shared it — and a precomputed table of common passwords works against the whole database at once. The salt is not a secret and does not need to be; it exists solely to make every hash unique so that each one has to be attacked separately.

What this means for you when reading a notice: “hashed” alone tells you nothing. “Salted and hashed with bcrypt / scrypt / Argon2” is genuinely reassuring. “Hashed with MD5” or “hashed with SHA-1” and no mention of a salt means you should treat the password as disclosed.

If not: the first block must be identical to this page — ana, ben and dan share a hash beginning f52fbd32, which is the SHA-256 of the string in PASSWORDS. If yours differs, the password strings were altered.

3
Put the response in an order that works

Go: the same folder.

Do: save this as order.py and run python3 order.py.

"""Doing the right things in the wrong order wastes the only advantage you have."""

ACCOUNTS = {
    "email":            {"resets": [], "reset_by": []},
    "the breached site":{"resets": [], "reset_by": ["email"]},
    "bank":             {"resets": [], "reset_by": ["email"]},
    "shopping":         {"resets": [], "reset_by": ["email"]},
}

def simulate(order):
    """An attacker is working in parallel. Anything not yet secured is exposed."""
    secured, lost = [], []
    for step, account in enumerate(order):
        # While you secure this one, the attacker uses the email you have not fixed.
        if "email" not in secured and account != "email":
            lost.append(account)
        secured.append(account)
    return lost

WRONG = ["the breached site", "bank", "shopping", "email"]
RIGHT = ["email", "the breached site", "bank", "shopping"]

for label, order in (("most people's order", WRONG), ("the right order", RIGHT)):
    lost = simulate(order)
    print("%-20s %s" % (label, " -> ".join(order)))
    print("%-20s accounts still reachable while you worked: %d %s"
          % ("", len(lost), lost if lost else ""))
    print()

print("Email first, always. Every other account has a 'forgot password' link")
print("that ends in your inbox, so securing anything else while the inbox is")
print("open secures nothing.")

You should see: three accounts left open while you worked on them:

most people's order  the breached site -> bank -> shopping -> email
                     accounts still reachable while you worked: 3 ['the breached site', 'bank', 'shopping']

the right order      email -> the breached site -> bank -> shopping
                     accounts still reachable while you worked: 0 

Email first, always. Every other account has a 'forgot password' link
that ends in your inbox, so securing anything else while the inbox is
open secures nothing.

The instinct is to start with the account that was breached, because that is the one in the email you just received. It is the wrong place to start. Every other account has a “forgot password” link that ends in your inbox, so an attacker with access to the inbox can undo each thing you secure, in the time it takes you to secure the next one.

The order, then: email first — new password, and two-factor authentication if it is not already on. Then the breached account. Then anywhere the same password was used. Then everything else, at your leisure.

If not: if both orders report 0, the condition checking whether email is already secured was inverted — the simulation only counts an account as exposed while the inbox is still open.

4
See what happens to the leaked list next

Go: the same folder.

Do: save this as stuffing.py and run python3 stuffing.py.

"""What happens to the leaked list, mechanically."""

LEAKED = [("sarah@example.com", "hunter2"), ("ben@example.com", "T7#qLm2v")]

TARGET_SITES = ["bank", "shopping", "streaming", "social", "webmail", "delivery"]

def stuff(pairs, sites):
    attempts = 0
    for email, password in pairs:
        for site in sites:
            attempts += 1
    return attempts

attempts = stuff(LEAKED, TARGET_SITES)
print("credentials in the dump :", len(LEAKED))
print("sites to try them on    :", len(TARGET_SITES))
print("login attempts           :", attempts)
print()
print("Now scale it. A dump of 5 million credentials against the same 6 sites:")
big = 5_000_000 * len(TARGET_SITES)
print("   attempts             :", format(big, ","))
print("   at 500 per second    :", format(int(big / 500 / 3600), ","), "hours")
print()
print("No password is guessed at any point. The attempts are simply replays")
print("of credentials people already chose, on sites that never leaked.")
print()
print("Which is why the only defence is that the password is not reused --")
print("and why the response to any breach notice includes every OTHER site")
print("where you used the same one.")

You should see: thirty million login attempts against sites that were never breached:

credentials in the dump : 2
sites to try them on    : 6
login attempts           : 12

Now scale it. A dump of 5 million credentials against the same 6 sites:
   attempts             : 30,000,000
   at 500 per second    : 16 hours

No password is guessed at any point. The attempts are simply replays
of credentials people already chose, on sites that never leaked.

Which is why the only defence is that the password is not reused --
and why the response to any breach notice includes every OTHER site
where you used the same one.

This is called credential stuffing, and the arithmetic is the whole of it. Nothing is cracked, guessed or broken: leaked email-and-password pairs are simply typed into other sites, automatically, and a small percentage work because people reuse passwords. The sites where those logins succeed never had a breach of their own and have nothing to apologise for.

Which is why the breach notice you received is about more than the site that sent it. The question it should prompt is not “what did they lose” but “where else did I use that password” — and if the honest answer is “I am not sure”, that uncertainty is the finding.

If not: the figures are plain multiplication over the two lists; if the total is 0, one of the lists is empty. The 500-per-second rate is an illustrative figure chosen to make the scale legible, not a measurement.

5
Separate what prevents fraud from what announces it

Go: the same folder.

Do: save this as monitoring.py and run python3 monitoring.py.

"""What 'credit monitoring' does, and the thing that actually works."""

#                                          prevents?  what it does
MEASURES = [
    ("credit monitoring / alerts",         False, "tells you AFTER something happened"),
    ("a credit freeze",                    True,  "stops new accounts opening in your name"),
    ("changing the breached password",     False, "closes that one account, nothing else"),
    ("changing it everywhere it was reused", True, "closes the credential-stuffing route"),
    ("adding 2FA to your email",           True,  "protects the account that resets the rest"),
    ("randomising security answers",       True,  "the only fix for unchangeable data"),
]

print("%-38s %-11s %s" % ("MEASURE", "PREVENTS?", "WHAT IT DOES"))
print("-" * 92)
for name, prevents, what in MEASURES:
    print("%-38s %-11s %s" % (name, "yes" if prevents else "no", what))

print()
print("measures that PREVENT :", sum(1 for _, p, _ in MEASURES if p))
print("measures that NOTIFY  :", sum(1 for _, p, _ in MEASURES if not p))
print()
print("Monitoring is what a breached company offers, because it is what they")
print("can buy on your behalf. The freeze is what stops the fraud, it is")
print("usually free, and you have to arrange it yourself.")

You should see: four preventive measures and two that only notify:

MEASURE                                PREVENTS?   WHAT IT DOES
--------------------------------------------------------------------------------------------
credit monitoring / alerts             no          tells you AFTER something happened
a credit freeze                        yes         stops new accounts opening in your name
changing the breached password         no          closes that one account, nothing else
changing it everywhere it was reused   yes         closes the credential-stuffing route
adding 2FA to your email               yes         protects the account that resets the rest
randomising security answers           yes         the only fix for unchangeable data

measures that PREVENT : 4
measures that NOTIFY  : 2

Monitoring is what a breached company offers, because it is what they
can buy on your behalf. The freeze is what stops the fraud, it is
usually free, and you have to arrange it yourself.

The first row is what the breached company will offer you, usually free for twelve months, and it is worth accepting — but understand what it is. Monitoring watches for your details being used and tells you afterwards. It does not stop anything.

The freeze is the preventive twin and almost nobody takes it up. In most countries you can restrict new credit being opened in your name — it is called a credit freeze or security freeze in some places, and a protective registration or notice of correction in others — it is usually free, and it can be lifted when you genuinely need credit. Search for the term your country's credit reference agencies use.

And the last row closes the permanent-fields problem from step 1: replace the answers to security questions with random strings stored in your password manager. “Mother's maiden name” is not a secret and never was; a random string in that box is.

If not: the counts come from the boolean in each row rather than from the text, which is deliberate — the first version of this script matched on the word “preventive” and counted the row that said not preventive.

🎉
Check yourself before moving on

Without scrolling up: you receive a notice saying a shopping site was breached, that passwords were “encrypted”, and offering twelve months of free credit monitoring. You used a unique password there and have never reused it. Is there anything left to do, and what would you want the notice to have said more precisely? Answer: yes, several things. The unique password means step 4's credential-stuffing risk does not apply to you, which is the single biggest win — but step 1 showed that the password is only one of the fields, and the permanent ones are still exposed: name, date of birth, and any security answers you gave that site, which unlock recovery on other sites asking the same questions. So the remaining actions are to randomise those security answers wherever you used real ones, make sure your email has two-factor authentication, and consider a credit freeze, since step 5 showed monitoring only tells you afterwards. As for the wording: “encrypted” is the imprecise part. Encryption is reversible with a key, and if that key was stored alongside the data, the passwords are readable. What you would want to see is whether the passwords were salted and hashed, and with which algorithm — step 2 showed that a salted bcrypt hash and an unsalted SHA-256 lead to entirely different conclusions.

Now do it without the page: take one breach notice you have actually received — most people have several in their inbox — and write out the two columns from step 1 for it: what can be changed and what cannot. Then do the single highest-value item from step 5's table that you have not already done. For most people that is two-factor authentication on their email, and it takes about three minutes.

Summary

  • The response depends on what leaked -- an email address is not a password, and a password is not a national identifier
  • Reused passwords are the real damage -- credential stuffing is what turns one breach into many
  • Revoke sessions as well as changing passwords
  • Freeze credit if an identifier leaked -- free, and stronger than paid monitoring
  • Expect targeted scams next -- accurate personal details now prove a leak, not legitimacy
  • Fix the structural problem -- a password manager, unique passwords, and passkeys where available
🎉
Treat it as a prompt, not a crisis.

You cannot control whether a company protects your data. You can control whether one leaked password opens thirty other doors. Fix the reuse once, and every future breach notification becomes something you read calmly rather than dread.