A AegiFlow
MEDIUMCVSS 6.5

GHSA-wmmp-3585-3rmp

Nodemailer: IDN/Punycode domain allow-list bypass leads to email delivery to an attacker-controlled domain

Published
2026-09-08
Modified
2026-09-08
Sources
github-advisory

Summary

### Summary Nodemailer resolves an international (IDN / non-ASCII) recipient **domain** to a different Punycode `xn--` label than every UTS‑46‑conformant parser (web browsers, the WHATWG URL Standard, Node's `url.domainToASCII`, Python's `idna`). Its address normalizer (`_normalizeAddress` in `lib/mime-node/index.js`) uses the bundled **raw RFC‑3492 Punycode codec with no UTS‑46 mapping/normalization**, so a domain that a standards‑compliant validator maps to a trusted domain is delivered by Nodemailer to a **different, attacker‑registrable domain**. An application that applies a domain allow‑list / same‑domain check to a recipient using a normal IDN‑aware parser (or that shows the normalized recipient to a user for confirmation) and then relies on Nodemailer to deliver to that domain can be induced to send email to an **unintended external domain**. This is the same weakness class as CVE‑2025‑13033 (Interpretation Conflict, CWE‑436) but reached through IDN/Punycode rather than quoted local‑parts, and it is not addressed by the 7.0.7 fix. Because the mismatch can be triggered with an **invisible** character (U+00AD SOFT HYPHEN) that UTS‑46 folds away to the *exact* trusted domain string, no visible look‑alike/homograph is required. ### Details `lib/mime-node/index.js` → `_normalizeAddress(address)` (around lines 1307–1346) splits the address at the last `@` and normalizes the domain like this: ```js // lib/mime-node/index.js try { if (/[\x80-￿]/.test(user)) { encodedDomain = punycode.toUnicode(domain.toLowerCase()); // line ~1338 } else { encodedDomain = punycode.toASCII(domain.toLowerCase()); // line ~1340 } } catch (_err) { // keep domain as supplied } return `${this._normalizeLocalPart(user)}@${encodedDomain}`; // line ~1346 ``` `punycode` here is the project’s bundled codec (`lib/punycode/`), which is a **pure RFC 3492 (Punycode) implementation**. The only normalization applied to the domain is `.toLowerCase()`. It performs **none of the UTS‑46 “IDNA2008 + compatibility processing” steps** that browsers and DNS‑facing resolvers apply before Punycode encoding, specifically: * removing **Ignored** code points such as `U+00AD` SOFT HYPHEN, * **Mapping** full‑width / compatibility characters to their canonical ASCII forms, * Unicode **NFC** normalization, * validity checks. As a result, for any domain containing a UTS‑46‑mapped or ‑ignored character, Nodemailer’s `punycode.toASCII(...)` produces a **different A‑label** than `url.domainToASCII(...)` (Node ≥ 7 / WHATWG), `new URL('http://'+domain)`, browsers, and Python’s `idna` (`uts46=True`). Nodemailer then uses its A‑label as: * the SMTP envelope recipient written to the wire as `RCPT TO: ` (`getEnvelope()` → `lib/smtp-connection/index.js` `_setEnvelope`), **and** * the address emitted in the `To:` / `From:` headers (`_convertAddresses`). So the domain a standards‑compliant validator computes and the domain Nodemailer actually delivers to **disagree**, on a syntactically valid, validator‑accepted address. Concrete divergences (verified on 9.0.6): | recipient (raw) | UTS‑46 parser (`url.domainToASCII`) | Nodemailer delivers to | |---|---|---| | `victim@compa{U+00AD}ny.com` (invisible soft hyphen) | `company.com` | `xn--company-pka.com` | | `victim@company.com` (full‑width) | `company.com` | `xn--mi7cd4afch9d.com` | | `user@exámple.com` (NFD `a`+U+0301) | `xn--exmple-qta.com` | `xn--example-vge.com` | This is the “Punycode / IDN parser discrepancy” technique documented in PortSwigger’s *Splitting the email atom* research (which produced e.g. Joomla CVE‑2024‑21725 and fixes in the PHP `idna_convert` library). The fix for CVE‑2025‑13033 (nodemailer 7.0.7) hardened the *quoted‑local‑part* path only; this IDN path is independent and still present in **9.0.6 (latest)** and, given the long‑standing use of the bundled RFC‑3492 codec, earlier releases. **Suggested remediation:** perform UTS‑46 processing before/at

Affected packages

EcosystemPackageAffected versionsFixed versions
npmnodemailer9.1.0

Remediation: Upgrade to 9.1.0 or later.

References

Includes data from the GitHub Advisory Database, licensed under CC-BY 4.0.