GHSA-7RQJ-J65F-68WH: Root-Cause Fix for NextAuth.js Email Homoglyph Account Takeover
Summary
The patch addresses a canonicalization-before-validation flaw in NextAuth.js email sign-in handling. Previously, Unicode homoglyphs of '@' could survive structural validation and later be normalized by downstream parsers into additional ASCII '@' characters, potentially redirecting passwordless login links to attacker-controlled recipients. The fix moves NFKC normalization ahead of validation in both the route-level handling and the shared default normalizer, and adds regression tests for U+FF20 and U+FE6B cases.
Analysis
Vulnerability
GHSA-7RQJ-J65F-68WH describes an account takeover condition in NextAuth.js passwordless email sign-in caused by validating email structure before Unicode canonicalization. The vulnerable flow accepted an identifier containing a Unicode homoglyph of @, such as U+FF20 FULLWIDTH COMMERCIAL AT, because the pre-patch logic only trimmed the string and then performed single-@ checks on the unnormalized value. A downstream address parser could later normalize that homoglyph into an ASCII @, effectively changing the address structure after validation and enabling delivery to attacker-controlled recipients.
This is a classic validate-before-canonicalize bug, explicitly called out in the patch comments as CWE-180. The security impact is high for magic-link authentication because the email address is the security boundary: if normalization changes recipient semantics after validation, the login token can be sent to an unintended mailbox.
// Pre-patch behavior from signin.ts and send-token.ts
const trimmedEmail = identifier.trim()
const trimmedEmail = email.toLowerCase().trim()
// Example malicious input from the added regression tests
attacker@evil.com@victim.company.comThe advisory and commits show that the exploit relies on Unicode compatibility normalization producing a real ASCII separator after the application has already accepted the address as structurally valid. See the advisory and code changes in GitHub Security Advisory, commit 19d2feb, and commit a63eee1.
Patch
The patch fixes the ordering error by applying Unicode NFKC normalization before validation in both affected code paths:
packages/next-auth/src/core/routes/signin.tsnow normalizesidentifierwith.normalize("NFKC")before trimming and subsequent validation.packages/core/src/lib/actions/signin/send-token.tsnow exportsdefaultNormalizerand applies.normalize("NFKC")before lowercasing, trimming, and structural checks.
// Patched behavior
const trimmedEmail = identifier.normalize("NFKC").trim()
const trimmedEmail = email.normalize("NFKC").toLowerCase().trim()The tests added in both packages are directly aligned with the exploit mechanics. They verify rejection of:
- U+FF20 FULLWIDTH COMMERCIAL AT:
attacker@evil.com@victim.company.com - U+FE6B SMALL COMMERCIAL AT:
attacker@evil.com﹫victim.company.com - Related parser-confusion cases such as quoted local parts and multiple ASCII
@symbols in the shared normalizer tests
This is source-grounded and consistent with the advisory: normalize first so compatibility characters are converted into their canonical ASCII forms before the single-@ and parser-confusion checks run. References: commit 19d2feb, commit a63eee1.
Review
Pros
- Fixes the root bug class by changing operation order from validate-then-canonicalize to canonicalize-then-validate.
- Applies the correction in both the route-level sign-in path and the shared core normalizer, reducing inconsistency between packages.
- Regression coverage is strong for the disclosed exploit primitive, including explicit tests for U+FF20 and U+FE6B.
- Patch comments clearly document the threat model and why downstream parser behavior matters.
- The change is low-risk operationally because it preserves the existing validation model while ensuring it runs on canonicalized input.
Cons
- The fix is scoped to NFKC-based canonicalization and existing structural checks; it does not constitute a full RFC-complete email parser or a comprehensive defense against all downstream MTA/parser discrepancies.
- The route-level patch uses
identifier.normalize("NFKC").trim()while the shared normalizer usesemail.normalize("NFKC").toLowerCase().trim(); although not inherently wrong, duplicated normalization logic across layers can drift over time. - The provided sources do not show broader end-to-end validation against every mail transport or provider-specific parsing edge case, so residual parser-confusion classes outside the tested cases may still depend on downstream behavior.
Verdict
Root-cause.
The patch directly addresses the underlying vulnerability mechanism identified in GHSA-7RQJ-J65F-68WH: Unicode compatibility characters were being validated before canonicalization. By moving NFKC normalization ahead of structural checks in both relevant code paths and adding targeted regression tests, the maintainers closed the specific homoglyph bypass that enabled passwordless account takeover. While this does not transform the email pipeline into a universal parser-hardening solution, it is the correct fix for the disclosed bug rather than a superficial blocklist or narrow bandaid.
Recommended Labs
Try this vulnerability pattern yourself with hands-on labs.
- Insecure Normalization.js
Best topical match for this NextAuth.js issue. The GHSA centers on Unicode homoglyph handling, normalization, and validation order around email identifiers. This lab directly targets insecure Unicode normalization in JavaScript and helps practice the defensive pattern of validating canonicalized input consistently before authentication/account-binding decisions.
- OAuth.js
Strong secondary match because the advisory leads to account takeover through an authentication flow. This JavaScript lab focuses on broken authentication/token validation themes, which complements the normalization bug by reinforcing defensive thinking around identity proof, token trust boundaries, and account ownership checks.
- Energy.js
Useful adjacent lab for hardening passwordless and email-based auth flows. Even though it focuses on user enumeration, it is relevant because robust authentication defenses should prevent identity leakage and inconsistent account-handling behavior alongside canonicalization fixes. Good companion exercise after the normalization lab.