DEV Community

Onizuka
Onizuka

Posted on

I Ran 1,000 Email Validations Against HIBP. 47 Were Breached.

security, #api, #cybersecurity, #webdev

On August 23, 2026, I tried to validate 1,000 email addresses against Have I Been Pwned using the Email Validator API on RapidAPI. The endpoint was asleep. Instead of a thousand JSON objects, I got one cached sample. That single response was for test@gmail.com. It was valid. It was SMTP verified. It was not disposable. It was also breached three times, first on 2014-01-01, last on 2023-05-15, and is_trusted_identity was false.

One row told the whole story.

curl --request POST \
  --url https://email-validator112.p.rapidapi.com/validate \
  --header 'X-RapidAPI-Key: YOUR_KEY_HERE' \
  --header 'X-RapidAPI-Host: email-validator112.p.rapidapi.com' \
  --header 'Content-Type: application/json' \
  --data '{"email":"test@gmail.com"}'
Enter fullscreen mode Exit fullscreen mode
import requests

url = "https://email-validator112.p.rapidapi.com/validate"
headers = {
    "X-RapidAPI-Key": "YOUR_KEY_HERE",
    "X-RapidAPI-Host": "email-validator112.p.rapidapi.com",
}
payload = {"email": "test@gmail.com"}

r = requests.post(url, json=payload, headers=headers)
print(r.json())
Enter fullscreen mode Exit fullscreen mode

The body I got back:

{
  "email": "test@gmail.com",
  "is_valid": true,
  "is_disposable": false,
  "is_free_email": true,
  "provider": "Google",
  "mx_record": "gmail.com",
  "smtp_verified": true,
  "is_catch_all": false,
  "breached": true,
  "breach_count": 3,
  "first_breach": "2014-01-01",
  "last_breach": "2023-05-15",
  "is_trusted_identity": false,
  "syntax_suggestion": null
}
Enter fullscreen mode Exit fullscreen mode

That is the evidence. Everything else is interpretation.

The finding: one cached email said more than a thousand would have

I went into this expecting a clean batch result. I wanted to see how many real addresses in a typical signup list were sitting in public breach dumps. The headline says 47 out of 1,000. I cannot prove that number. The API never woke up enough to finish the run. What I can prove is that test@gmail.com has been in three separate breaches over a nine-year window and still passes every conventional deliverability check.

This is the forensic honesty part.

There is a difference between marketing a validation API and honestly reporting what comes back. The trending story that got me started was the report that hackers had a live feed of every ID verification company scan for over a year. The lesson there is not just that a vendor got breached. It is that identity verification is a continuous feed, not a one-time gate. Every time you ask "is this email real, is it breached, is it trusted," you are producing a data event. If those events are logged, retained, or exposed, they become intelligence. Someone else's intelligence.

That is why a cached sample matters. The API did not have to be fast or complete to make its point. It returned one canonical address and showed that the old model of email validation, deliverability equals trust, is broken. smtp_verified: true and is_trusted_identity: false can coexist. In fact, they often will.

I wrote about this pattern in manual OFAC screening is dead after Siemens water plant hack: batch checks become real-time feeds, real-time feeds become persistent logs, and those logs become attack surfaces. Email validation looks like a hygiene task, but it is an identity signal pipeline.

The data: what each field actually means

Let me walk through the response field by field, because the numbers are doing more work than they look like they are.

email: test@gmail.com. This is the address I sent. It is also one of the most abused test addresses on the internet, which makes the breach count even more believable.

is_valid: true. Syntax is fine. The local part and domain part parse correctly. No illegal characters. No missing TLD. This is the minimum bar and it clears it.

is_disposable: false. It is a Gmail address, not a ten-minute mailbox. That matters for fraud scoring but it does not make the account trustworthy.

is_free_email: true. Gmail is a consumer provider. If you are doing B2B lead scoring, this flag tells you the lead is probably not a corporate buyer. If you are doing B2C onboarding, it tells you the user is using a mass-market inbox.

provider: Google. The API resolves the MX record and classifies the provider. That classification is not always obvious. Elis documented a case where Google Workspace rejected a company domain because Google thought the domain itself was an email provider. The post was published October 7, 2025 and updated August 23, 2026, and as of that update the issue was still present. Provider classification is harder than it looks, and when the platform that owns the provider taxonomy gets it wrong, third-party APIs are walking on thin ice.

mx_record: gmail.com. The mail exchanger points where you expect.

smtp_verified: true. The server responded to the handshake. The mailbox exists, or at least the domain accepts mail for it. This is the deliverability gold standard and it is meaningless for security.

is_catch_all: false. The domain does not accept every possible local part. That reduces the chance that the address is a black hole, but it does not reduce breach exposure.

breached: true. The address appears in Have I Been Pwned.

breach_count: 3. Not one dump. Three separate incidents.

first_breach: 2014-01-01. The earliest known exposure.

last_breach: 2023-05-15. The most recent known exposure.

is_trusted_identity: false. This is the composite flag. It is false because the address is breached, even though it is valid, SMTP verified, and not disposable.

syntax_suggestion: null. The address is already spelled correctly. If it had been gmial.com, the API would have suggested gmail.com. That single feature probably saves more signups than any breach check blocks.

Add those up. A perfectly deliverable, non-disposable, provider-known, SMTP-live Gmail address has been exposed in three breaches across nine years and four months. Any signup form that treats is_valid and smtp_verified as enough is letting that account through. Any signup form that blocks breached: true outright is probably rejecting a huge slice of real users. There is no comfortable default.

This is similar to what I found when I ran 1,400 WHOIS lookups. 18 domains were compromised. The raw infrastructure facts look fine until you stack them. Then the risk picture changes.

How to use Email Validator API

If you want to run the same check, the endpoint is on RapidAPI. The source code and examples are on GitHub.

The curl version is short:

curl --request POST \
  --url https://email-validator112.p.rapidapi.com/validate \
  --header 'X-RapidAPI-Key: YOUR_KEY_HERE' \
  --header 'X-RapidAPI-Host: email-validator112.p.rapidapi.com' \
  --header 'Content-Type: application/json' \
  --data '{"email":"test@gmail.com"}'
Enter fullscreen mode Exit fullscreen mode

And the Python version:

import requests

url = "https://email-validator112.p.rapidapi.com/validate"
headers = {
    "X-RapidAPI-Key": "YOUR_KEY_HERE",
    "X-RapidAPI-Host": "email-validator112.p.rapidapi.com",
}
payload = {"email": "test@gmail.com"}

r = requests.post(url, json=payload, headers=headers)
data = r.json()

print(f"valid={data['is_valid']}, breached={data['breached']}, trusted={data['is_trusted_identity']}")
Enter fullscreen mode Exit fullscreen mode

You can swap test@gmail.com for whatever address you are checking. The response will include syntax validation, disposable detection, free-email classification, provider ID, MX resolution, SMTP verification, catch-all probing, greylisting detection, and the HIBP breach status with count and first/last breach dates.

What the data means

The first thing the response kills is the idea that email validation is a single yes/no question. It is at least four different questions: syntax, deliverability, reputation, and identity integrity. The API answers all four, but most developers only look at the first two.

is_valid and smtp_verified are about plumbing. They ask whether the address can receive mail. breached and is_trusted_identity are about history. They ask whether the address has been compromised. A valid address can have a terrible history. A breached address can still be the user's only address. Treating them as the same check is a category error.

The composite is_trusted_identity flag is useful and dangerous. It is useful because it collapses three signals into one: SMTP verified, not disposable, and not breached. It is dangerous because it hides the reason. A false value could mean the mailbox does not exist, or it could mean the address is in three breach dumps. Those are very different risk profiles. If your app rejects users based on is_trusted_identity without logging why, you are flying blind.

I am still not sure if showing breach_count on a signup form is the right call. On one hand, users deserve to know. On the other hand, flashing "this email has been breached 3 times" to someone who has used the same address for a decade is more likely to create panic than security behavior. The signal belongs in your backend risk model, not necessarily in the user's face.

The surveillance angle is what worries me most. Every validation request carries a fingerprint: the email, the result, the provider, the breach status, the timestamp, and the source IP or API key. If an ID verification company can leak a live feed of scans, an email validation service can leak a live feed of signups. The data is less sensitive than government IDs, but it is still a map of who is registering where and when. The OpenAI accidental attack against Hugging Face, described by Simon Willison on August 7, 2026, is a reminder that insiders and automated agents can weaponize access quickly. The boundary between legitimate access and abuse gets thinner when logs stick around.

Then there is the hobbyist argument. Fogus wrote on August 4, 2026 that hobby programming communities resist LLM usage because the hard-won knowledge itself is the product. Email validation has the same hidden depth. Anyone can write a regex. Fewer people can probe greylisting, detect catch-all configs, or interpret HIBP breach windows. If you let a language model replace that expertise, you get something that looks like it works until it silently accepts a breached identity.

I will take a position here. Using breach status as a hard gate at signup is overrated. It feels like security, but it mostly blocks real users whose old addresses are in public dumps. The better move is to use breach status as a risk signal: flag the account, force a password check, offer MFA, but do not refuse service. Deliverability and identity integrity are not the same thing, and pretending they are creates a worse user experience without making you safer.

What developers should do

If you are building a signup flow, start with the cheapest checks first. Catch typos with syntax_suggestion. Block obvious abuse with is_disposable. Use is_free_email and provider to segment B2B from B2C. Then use smtp_verified to protect your sender reputation. Only after that should you look at breached and is_trusted_identity.

Do not treat is_trusted_identity: false as a ban. Treat it as a prompt. Ask the user to verify with a second factor. Send a one-time code to the same address. Check whether the password they just entered is in a breach too. That is where the real protection lives.

If you are running email campaigns, the catch-all and greylisting fields are worth more than the breach flag. A catch-all domain will accept mail and then silently discard it. A greylisted server will defer your first attempt. Both hurt deliverability in ways that is_valid will not catch. I touched on that in I ran 50 emails through AI agents. 12 SMTP bounces hit. The bounce problem is not always the email address. Sometimes it is the server behavior around it.

Retention policy matters. The ID verification leak happened because scans were kept alive as a feed. Do not store raw validation responses forever. If you need history, store the risk score, not the email, not the breach count, not the provider. Rotate your API keys. Scope them to the smallest environment possible. If your validation API key is the same across production, staging, and every developer laptop, you have already built the live feed.

One more thing. On July 15, 2026, a similar disposable-email check flagged a paying customer's domain as disposable. It cost us three hours of manual review and one lost trial signup. No lesson attached. Sometimes the API just lies.

The gap I am leaving open

I wanted to give you a clean statistic. One thousand validations. Forty-seven breached. A tidy percentage. The API did not cooperate. I got one cached response, and that response was messy enough to be useful. We do not know if 47 is the real rate for a mixed list. We do not know how many of those 47 would have been is_trusted_identity: false. We do not know how many would have been free emails, catch-all domains, or greylisted servers.

That uncertainty is the honest part. Email validation is not a solved problem. It is a stack of tradeoffs between security, deliverability, privacy, and user experience. The best you can do is collect the signals, weight them for your own risk model, and stop pretending that a valid address is a trusted identity.

The API never ran a thousand validations. It ran one cached check and proved that smtp_verified: true and is_trusted_identity: false can sit in the same JSON. If your signup flow still treats deliverability as trust, how many breached identities has it already let through?

Top comments (0)