There have now been three separate advisories about next-auth delivering a magic link to somebody who did not ask for it. The 2022 one, the 2025 one, and CVE-2026-73420 in July.
Here is the part worth your attention. Each fix is where the next bug lives.
TL;DR
Auth.js counts the @ symbols in a submitted address and rejects anything without exactly one. A fullwidth commercial at (U+FF20) is not an ASCII @, so it sails through that count. A mail library that then applies Unicode NFKC normalization sees two separators and can deliver the sign-in link to the attacker's domain. Patched in next-auth 4.24.15, 5.0.0-beta.32 and @auth/core 0.41.3, all published 20 July 2026. The at-sign counter it defeats was itself added in October 2025 to fix the previous misdelivery bug.
Am I affected
npm ls next-auth @auth/core
You are in the published affected ranges if you are on:
next-auth>= 4.10.3 and < 4.24.15next-auth>= 5.0.0-beta.1 and <= 5.0.0-beta.31@auth/core>= 0.1.0 and < 0.41.3
That last floor is worth reading twice. @auth/core 0.1.0 shipped in December 2022, so "every version before 0.41.3" means every version there has ever been.
You also have to be using the email or magic-link sign-in path. If your app is OAuth-only, this specific advisory is not yours to worry about.
npm install next-auth today resolves to 4.24.15, and next-auth@beta resolves to 5.0.0-beta.32. Both are patched. This bug covered every install path right up until 20 July 2026, which is why an app scaffolded in June and never updated is the one to check. There is still no stable v5: the beta dist-tag is the v5 release line.
The bug, precisely
The default normalizer trims the address, counts @, and refuses anything that does not have exactly one. Here it is as shipped in next-auth 4.24.12, comments included:
const normalizer: (identifier: string) => string =
provider.normalizeIdentifier ??
((identifier) => {
const trimmedEmail = identifier.trim()
// Validate email format according to RFC 5321/5322
// Reject emails with quotes in the local part to prevent address parser exploits
// Reject multiple @ symbols which could indicate an exploit attempt
const atCount = (trimmedEmail.match(/@/g) ?? []).length
if (atCount !== 1) {
throw new Error("Invalid email address format.")
}
// ...
"Reject multiple @ symbols which could indicate an exploit attempt." The check does exactly that, and the attack is built so that at the moment of counting there is only one.
U+FF20 FULLWIDTH COMMERCIAL AT is a different codepoint from U+0040. The regex /@/g does not match it. But under NFKC normalization, the form mail libraries apply when they handle internationalized addresses, it folds into a plain ASCII @.
So the string is valid on the way in and a different string on the way out.
We ran the shipped normalizer against three payloads to see which survive:
"victim@example.com,attacker@evil.com" -> REJECTED: Invalid email address format.
"victim@example.com<U+FF20>attacker.evil"
accepted as -> "victim@example.com@attacker.evil"
after NFKC -> "victim@example.com@attacker.evil" | @ count: 2
domain a last-@ parser delivers to -> attacker.evil
"victim@example.com<U+FE6B>attacker.evil"
accepted as -> "victim@example.com﹫attacker.evil"
after NFKC -> "victim@example.com@attacker.evil" | @ count: 2
domain a last-@ parser delivers to -> attacker.evil
Two things fall out of that. The 2022 comma payload is genuinely dead, so the older fix works. And the advisory names U+FF20 as the example, but it is not the only one: U+FE6B SMALL COMMERCIAL AT folds to the same character and behaves identically. Anything that NFKC-folds to @ is a candidate.
The victim does nothing here. The attacker submits the crafted address to your public sign-in endpoint, and the link for the victim's account is sent somewhere else. That is the UI:N in the CVSS 4.0 vector, and it is why the score is 9.1.
The chain, which is the actual story
We bisected the published tarballs to find where each check entered the code. The dates are the npm publish times.
CVE-2022-35924. Submitting victim@example.com,attacker@evil.com sends the sign-in mail to both addresses. Fixed in next-auth 4.10.3, which introduces the pluggable normalizeIdentifier hook and a default that lowercases, splits on @, and drops everything after a comma in the domain. There is no at-sign counter yet.
GHSA-5jpx-9hw9-2fx4, "NextAuthjs Email misdelivery Vulnerability". A quoted local part, "e@attacker.com"@victim.com, reaches the wrong recipient. Fixed in 4.24.12, which adds the quote rejection and the atCount !== 1 check to that same default normalizer.
CVE-2026-73420. A homoglyph defeats the at-sign counter added nine months earlier, because the count happens before normalization rather than after. Fixed in 4.24.15 by normalizing first.
Three advisories, three fixes, and the same shape each time: a string check added in front of a mail library that parses addresses by different rules. Every fix closed the exact input that got reported. None of them closed the gap between what the validator thinks the address says and what the sender thinks it says.
That gap has a name. CWE-180, "Incorrect Behavior Order: Validate Before Canonicalize." Canonicalize first, then validate, and all three bugs disappear at once. That is what 4.24.15 finally does.
Ask an assistant to validate an email and you reliably get a regex or a split("@") length check, applied to the raw input. The same ordering mistake, in the same place. We see hand-rolled versions of this exact check in scans all the time, sitting in front of password reset and invite flows that were never in an advisory because nobody looked.
What being on an affected version does not tell you
The exploit needs a second thing your version number cannot answer: your mail sender has to normalize the recipient address.
The advisory says senders that handle internationalized email commonly apply NFKC. It does not name one. That uncertainty is encoded right in the CVSS vector as AT:P, attack requirements present.
We have since measured one of them. Nodemailer does not normalize: we passed the fullwidth U+FF20 payload through nodemailer 9.0.0 and 9.1.0 and the character survived untouched in both, so the address never gains a second separator and the chain does not complete. If you use the Nodemailer provider, this attack is not your exposure. The full test, and the two separate nodemailer domain-parsing bugs we found along the way, are written up separately. Resend, SendGrid and Postmark are still unmeasured, so treat those stacks as unknown rather than safe.
There is also no public evidence anyone has exploited it. No proof of concept shipped with the advisory, which is a contrast with the 2025 misdelivery bug that did ship one, and it is not on CISA's Known Exploited Vulnerabilities list.
None of that is a reason to stay unpatched. It is a reason not to tell your users you were breached.
If you want to know rather than assume, send yourself one:
// Send to an address containing U+FF20 at a domain you control.
// If it arrives at the SECOND domain, your sender normalizes.
const probe = "you@your-domain.test@second-domain-you-own.test";
await mailer.send({ to: probe, subject: "normalization probe", text: "arrived" });
Use two domains you actually own. Never point a probe at somebody else's.
Fix it
Upgrade. npm i next-auth@4.24.15 on v4, npm i next-auth@beta for v5, or @auth/core@0.41.3 if you use the core package directly.
If you cannot upgrade today, supply your own normalizeIdentifier that canonicalizes before it validates. In v4 that goes on EmailProvider; in v5 it goes on the Nodemailer or Resend provider.
normalizeIdentifier(identifier: string) {
// Canonicalize FIRST. This is the whole fix.
const email = identifier.normalize("NFKC").toLowerCase().trim();
if (email.includes('"')) throw new Error("Invalid email address format.");
if ((email.match(/@/g) ?? []).length !== 1) {
throw new Error("Invalid email address format.");
}
// Optional, and worth it if you do not need international addresses:
if (/[^\x20-\x7E]/.test(email)) throw new Error("Invalid email address format.");
return email;
}
While you are in there, check the rest of the batch. Four advisories shipped in these same patch releases. One of them, CVE-2026-73421, lets a configuration error make if (req.auth) true for every request, which we wrote up separately. It affects v5 only and is arguably the worse of the two.
Grep your own code for the same ordering. split("@"), .length !== 1, or an email regex applied to raw input, anywhere near a password reset, an invite, or a team join flow, is the same bug without a CVE number.
Am I affected by CVE-2026-73420?
Run npm ls next-auth @auth/core. You are in the affected range on next-auth 4.10.3 up to but not including 4.24.15, on any next-auth 5.0.0-beta.1 through beta.31, or on @auth/core below 0.41.3. That @auth/core floor of 0.1.0 means effectively every version ever published. You also need to be using the email or magic-link provider at all. OAuth-only apps are not in scope for this one.
What actually goes wrong?
The default email normalizer counts @ symbols and rejects anything that does not have exactly one. A fullwidth commercial at, U+FF20, is not an ASCII @, so an address containing it passes that count with one real @. When a downstream mail library applies Unicode NFKC normalization, the fullwidth character folds into a plain @, the address now has two separators, and the sign-in link can be delivered to the second domain.
Is my app actually exploitable, or just on an affected version?
Being on an affected version is not the same as being exploitable. The attack also needs your downstream mail sender to apply Unicode normalization to recipient addresses. We tested nodemailer and it does not: the fullwidth U+FF20 survives untouched in 9.0.0 and 9.1.0, so the chain does not complete through the Nodemailer provider. Resend, SendGrid and Postmark remain untested, so treat those as unmeasured until you check them.
Was this exploited in the wild?
There is no public evidence of exploitation. No proof of concept shipped with the advisory, unlike the 2025 misdelivery bug in the same code path, and the CVE is not on CISA's Known Exploited Vulnerabilities list. The 9.1 rating describes what the bug allows, not something that is known to have happened.
Why does patching keep introducing the next version of this bug?
All three fixes are string checks bolted in front of a mail library that parses addresses by a different set of rules. The 2022 fix stripped commas. The 2025 fix counted at-signs and rejected quotes. Each closed the exact input that had been reported and left the general problem in place, which is that your validator and your mail sender disagree about what the address says.
Check what your login flow actually exposes
A scan reads your deployed sign-in surface for missing rate limits, leaky error messages, and endpoints that accept more than they should.