KEVWire

Security intelligence, with context.

Response snapshot Oct 27, 2026, 16:31 UTC

Back to the wire
Permalink
VulnerabilityGHSA-g73g-hqqh-jr95

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

Nodemailer's address parser has three published, patched fixes for RFC 5322 comment `( ...

At a glance

Exploited
Not known
Severity
Not scored yet
Chance of exploitation
Not estimated
Sources
1 source

What to do

GitHub lists first patched versions: nodemailer: 10.0.13. Confirm the affected range and upgrade guidance in the exact advisory.

Read GitHub-reviewed advisory GHSA-g73g-hqqh-jr95 (opens a new tab)

What happened

## 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 `. When the input inside the angle brackets itself contains an RFC 5322 comment, e.g.

Continue reading

``` Legit User ```

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: [SP ]`, 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 ` '`. 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 ' `, 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 ')); // [ { 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' } ] { const t = nodemailer.createTransport({ jsonTransport: true }); const info = await t.sendMail({ from: 'app@good-corp.com', to: 'Legit User ', 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 Subject: hi ```

### Wire-level confirmation against a real SMTP server

```js const net = require('net'); const nodemailer = require('nodemailer');

const server = net.createServer(sock => { let buf = ''; sock.write('220 sink ESMTP\r\n'); sock.on('data', d => { buf += d; let i; while ((i = buf.indexOf('\r\n')) >= 0) { const line = buf.slice(0, i); buf = buf.slice(i + 2); const u = line.toUpperCase(); if (u.startsWith('EHLO')) sock.write('250-sink\r\n250 8BITMIME\r\n'); else if (u.startsWith('MAIL')) sock.write('250 ok\r\n'); else if (u.

startsWith('RCPT')) { console.log('wire RCPT TO :', line); sock.write('250 ok\r\n'); } else if (u.startsWith('DATA')) sock.write('354 go\r\n'); else if (line === '.') sock.write('250 ok\r\n'); else if (u.startsWith('QUIT')) { sock.write('221 bye\r\n'); sock.end(); } else sock.write('250 ok\r\n'); } }); });

server.listen(0, '127.0.0.1', async () => { const t = nodemailer.createTransport({ host: '127.0.0.1', port: server.address().port, secure: false }); const info = await t.sendMail({ from: 'app@good-corp.com', to: 'Legit User ', subject: 'hi', text: 'x' }); console.log('envelope.to:', info.envelope.to); t.close(); server.close(); }); ```

Observed:

Affected package ranges reported by GitHub: nodemailer: >= 3.0.0, <= 10.0.12

Related technology

Sources

  1. Primary source

    GitHub-reviewed advisory GHSA-g73g-hqqh-jr95 (opens a new tab)

    The public advisory supplies the description, affected package/version ranges, and any listed CVSS metric. GitHub review is not local review.

    Published Checked

  2. Primary source

    GitHub public advisory API page (opens a new tab)

    The API page supplied GHSA-g73g-hqqh-jr95, its actual identifiers, source dates, and structured metrics.

    Published Date not suppliedChecked

Cite this record

For factual claims, cite the primary sources above; cite KEVWire for its compilation, dates, and classifications.

APA

KEVWire. (2026, October 27). Nodemailer: RFC 5322 comment inside an angle-bracket address is never stripped. https://kevwire.com/record/GHSA-g73g-hqqh-jr95

MLA

"Nodemailer: RFC 5322 comment inside an angle-bracket address is never stripped." KEVWire, 27 Oct. 2026, kevwire.com/record/GHSA-g73g-hqqh-jr95.

Chicago

KEVWire. "Nodemailer: RFC 5322 comment inside an angle-bracket address is never stripped." Last modified October 27, 2026. https://kevwire.com/record/GHSA-g73g-hqqh-jr95.

Evidence summary (plain text)

Nodemailer: RFC 5322 comment inside an angle-bracket address is never stripped (Oct 27, 2026).
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 `. When the input inside the angle brackets itself contains an RFC 5322 comment, e.g.
Sources:
GitHub-reviewed advisory GHSA-g73g-hqqh-jr95: https://github.com/advisories/GHSA-g73g-hqqh-jr95
GitHub public advisory API page: https://api.github.com/advisories?type=reviewed&per_page=100&sort=updated&direction=desc
Record ID: GHSA-g73g-hqqh-jr95.
Record origin: Ingested record.
AI evidence: Not assessed.
Known exploitation: Not assessed.
Zero-day status: Not assessed. KEV membership alone does not establish zero-day status.
About this data

This record was ingested from a data source. Retrieved: Oct 27, 2026, 16:25 UTC. Ingestion is not an independent editorial investigation.

A CISA KEV listing documents known exploitation, not necessarily a zero-day; absence from the catalog does not prove a vulnerability has never been exploited.

Technology tags indicate possible relevance, not verified exposure. Confirm the affected product, version, and configuration against your own inventory; KEVWire does not scan your systems.

A personal reading checklist on this device, not verified remediation.

Record JSON

Your account

Email alerts

Email alerts are not available yet. To follow your stack today, use its RSS feed, which honors your filters, or connect an AI agent through the API.

Suggest an improvement

Ideas, missing sources, corrections, and bugs are all welcome. No account needed. Suggestions are reviewed by a person before anything changes.

Record details

Record detail