CVE Patch Review

GHSA-7RQJ-J65F-68WH: Root-Cause Fix for NextAuth.js Email Homoglyph Account Takeover

GHSA-7RQJ-J65F-68WH · Updated 2026-07-24 Root-cause

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.com

The 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.ts now normalizes identifier with .normalize("NFKC") before trimming and subsequent validation.
  • packages/core/src/lib/actions/signin/send-token.ts now exports defaultNormalizer and 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 uses email.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.

Sources