Patch Forecast
Yellow warning
Plan into this sprint.

GHSA-g73g-hqqh-jr95

Nodemailer: RFC 5322 comment inside an angle-bracket address is never stripped

Risk score
42/100
CVSS
5.3 v3.1
Published
27 Oct 2026

Open in the live forecast Today's forecast

What to know

What it is
Nodemailer's address parser has three published, patched fixes for RFC 5322 comment ( ... ) handling inside an addr-spec: GHSA-cc9r-2j5m-2m83 (comment glued two domain atoms into one), GHSA-g57g-f23g-4646 (the same glue for a quoted local part), and the related ReDoS fixes GHSA-prgh-xp8r-p3m5 / GHSA-v53p-9fqp-m79j. Al…
Affected
nodemailer (npm) >= 3.0.0, <= 10.0.12
Fix
Upgrade nodemailer to 10.0.13 or later.
Exploitation
Public proof of concept

Story map

Story map: nodemailerTHE FLAWSTHE FIXGHSA-g73g-hqqh-jr95Nodemailer: RFC 5322comment inside anangle-bracket address is…CVSS 5.3CVE-2026-90776Nodemailer addressparser:O(n^2) on comment-joinedaddresses enables a remote…CVSS 7.5 · EPSS 1%CVE-2026-110871Nodemailer: Quadratic (O(n^2))DKIM header unfolding lets alarge folded header cause…CVSS 6.5 · EPSS 0%GHSA-v53p-9fqp-m79jNodemailer: Quadraticbacktracking in theaddressparser free-text…CVSS 7.5Upgrade nodemailer10.0.10or later on your release lineWho is exposednodemailer (npm) >= 9.1.0, <=10.0.4AMBER · PATCH THIS WEEKnodemailernodemailer/lib/addressparserparses one shape of address inO(n^2) time. A single ~640 KBaddress value blocks the Node.jsevent loop for roughly 7 seconds.And it is reachable without auth:mailparser feeds inbound email…Where it standsPublic proof of conceptCONNECTS TOSame weaknessCWE-436 (CWE-436)25 other warnings in 30 days →
Story map: the flaws, the fix, how it unfolded and what it connects to. Boxes link to the record or article.

At a glance

nodemailer: Amber, patch this week. nodemailer/lib/addressparser parses one shape of address in O(n^2) time. A single ~640 KB address value blocks the Node.js event loop for roughly 7 seconds. And it is reachable without auth: mailparser feeds inbound email headers straight into this parser, so one crafted email is enough to stall a service that parses …

The flaws

  • GHSA-g73g-hqqh-jr95: Nodemailer: RFC 5322 comment inside an angle-bracket address is never stripped (CVSS 5.3)
  • CVE-2026-90776: Nodemailer addressparser: O(n^2) on comment-joined addresses enables a remote DoS (reachable via mailparser) (CVSS 7.5 · EPSS 1%)
  • CVE-2026-110871: Nodemailer: Quadratic (O(n^2)) DKIM header unfolding lets a large folded header cause remote denial of service during signing (CVSS 6.5 · EPSS 0%)
  • GHSA-v53p-9fqp-m79j: Nodemailer: Quadratic backtracking in the addressparser free-text fallback allows remote denial of service (CVSS 7.5)

The fix

Upgrade nodemailer: 10.0.10 or later on your release line

Who is exposed: nodemailer (npm) >= 9.1.0, <= 10.0.4

Where it stands

  • Public proof of concept

Connects to

Why it is Yellow

  • CVSS 5.3 medium
  • Public exploit or proof of concept referenced
  • On your watchlist: npm

Affected packages

PackageEcosystemVulnerableFixed in
nodemailernpm>= 3.0.0, <= 10.0.1210.0.13

References

Original description

## Summary Nodemailer's address parser has three published, patched fixes for RFC 5322 comment `( ... )` handling inside an addr-spec: GHSA-cc9r-2j5m-2m83 (comment glued two domain atoms into one), GHSA-g57g-f23g-4646 (the same glue for a quoted local part), and the related ReDoS fixes GHSA-prgh-xp8r-p3m5 / GHSA-v53p-9fqp-m79j. All three fixes live in `_handleAddress()`'s `joins` / `_recoverAddrSpec()` logic in `src/addressparser/index.ts`, and that logic is exercised whenever the local text-accumulation path in the tokenizer runs. That logic is **never reached at all** for the single most common way a recipient is written: a display name followed by an angle-bracket address, `Name <local@domain>`. When the input inside the angle brackets itself contains an RFC 5322 comment, e.g. ``` Legit User <user@good-corp.com(x)evil.com> ``` the comment is not recognized as an operator, is not stripped, and the parenthesis characters survive **verbatim** inside the parsed address. That value is used as-is for `envelope.to` and is written raw onto the wire in the `RCPT TO` SMTP command. I confirmed this end to end: * against nodemailer's own `jsonTransport`/`streamTransport` (`envelope.to` and the emitted `To:` header both carry the malformed value), and * against a real SMTP server (`aiosmtpd`), where the malformed value is fatal to the transaction (`501 Syntax: RCPT TO: <address> [SP <mail-parameters>]`, all recipients rejected). This reproduces unmodified on every version I tested from `6.9.16` through the current `10.0.12` HEAD, i.e. it predates and survives all three of the address-parser advisories above unchanged, because it is a different root cause in a code path those fixes never touch. ## Root cause ### 1. The tokenizer has a single-slot operator stack `src/addressparser/index.ts`, `Tokenizer.checkChar()`: ```ts } else if (!this.operatorExpecting && !this.inDomainLiteral && chr in this.operators) { this.node = { type: 'operator', value: chr }; this.list.push(this.node); this.node = null; this.operatorExpecting = this.operators[chr]; this.escaped = false; return; } ``` (currently around line 670) `operatorExpecting` is a single string, not a stack. Once `<` opens the address scope, `operatorExpecting` is set to `'>'`. The branch above that recognizes a *new* operator character (such as `(`) is gated on `!this.operatorExpecting`, which is now false. So while inside `<...>`, a `(` is never tokenized as an operator at all — it is just an ordinary character appended to the running text token, together with everything up to the next `>`. The RFC 5322 comment is not recognized, not entered, and not stripped: it rides straight through as literal text. ### 2. The `_handleAddress` state machine loses the `address` state on any operator boundary `src/addressparser/index.ts`, inside `_handleAddress()` (currently around line 365-388): ```ts switch (token.value) { case '<': state = 'address'; insideQuotes = false; break; case '(': state = 'comment'; insideQuotes = false; break; ... default: state = 'text'; insideQuotes = false; break; } ``` Even where a comment *is* recognized (outside angle brackets), closing it with `)` falls through to `default` and resets `state` to `'text'`, not back to whatever state was active before the comment opened. This is why the fixed cases (bare address, quoted local part) only work at all: the leaked comment tail lands in the free-`text` bucket, where `_recoverAddrSpec()`/the ADDR_SPEC extraction logic can find and strip it. Inside `<...>`, none of that applies in the first place (point 1), so there is no text-bucket safety net to fall back on — the whole point 1/point 2 interaction is what makes the angle-bracket case categorically different from, and unprotected by, the three existing fixes. ### 3. The one existing safety net requires whitespace, and this value has none `_recoverAddrSpec()` (currently around line 283) is the function all three prior fixes rely on to peel a clean addr-spec back out of a polluted value. Its very first line is: ```ts function _recoverAddrSpec(data: { address: string; text: string }): void { if (!HAS_WHITESPACE.test(data.address)) { return; } ... ``` `user@good-corp.com(x)evil.com` contains no whitespace at all — the comment content `x` had none, and nothing in the angle-bracket path inserts any — so `_recoverAddrSpec` bails out immediately and never even looks at the value. There is no cleanup path for this shape. ## Proof of Concept Environment: Node.js, current `master` (commit `30e4cfd5902fd5da4d1bc76dc8e4bc92d288adef`, package.json version `10.0.12`), no special transport configuration required. ### Parser-level confirmation ```js const addressparser = require('nodemailer/lib/addressparser'); console.log(addressparser('Legit User <user@good-corp.com(x)evil.com>')); // [ { address: 'user@good-corp.com(x)evil.com', name: 'Legit User' } ] ``` Compare with the already-fixed, unbracketed form of the exact same bytes: ```js console.log(addressparser('user@good-corp.com(x)evil.com')); // [ { address: 'user@good-corp.com', name: 'evil.com' } ] <- correctly stripped ``` ### `sendMail` / envelope confirmation (jsonTransport, no network) ```js const nodemailer = require('nodemailer'); (async () => { const t = nodemailer.createTransport({ jsonTransport: true }); const info = await t.sendMail({ from: 'app@good-corp.com', to: 'Legit User <user@good-corp.com(x)evil.com>', subject: 'hi', text: 'x' }); console.log('envelope.to:', info.envelope.to); })(); ``` Observed output: ``` envelope.to: [ 'user@good-corp.com(x)evil.com' ] ``` The raw `streamTransport` message shows the same value in the emitted header: ``` From: app@good-corp.com To: Legit User <user@good-corp.com(x)evil.com> Subject: hi ``` ### Wire-level confirmation against a real S

Last activity 27 Oct 2026. Patch Forecast updates every hour.