Sep 9, 2026·5 min read·1 visit
Nodemailer versions prior to 9.1.0 are vulnerable to remote Denial of Service (DoS) due to quadratic time complexity ($O(n^2)$) in the address parser and envelope generation modules, which blocks the single-threaded Node.js event loop.
An algorithmic complexity vulnerability in Nodemailer before version 9.1.0 allows remote attackers to block the Node.js event loop. This denial of service is triggered by processing large or complex lists of email addresses, leading to quadratic resource consumption.
Nodemailer is a widely adopted module for sending emails within Node.js applications. It relies on internal components such as the addressparser module and the mime-node engine to parse headers, format recipient lists, and construct SMTP-compliant envelopes. These components process inputs that are frequently sourced directly from untrusted clients, such as user registration forms, contact portals, or inbound mail relays.\n\nThe vulnerability arises due to inefficient algorithms implemented within these processing modules. When an application processes address fields containing thousands of elements or specifically structured fragments, the execution time increases quadratically rather than linearly. Because Node.js utilizes a single-threaded execution model, any prolonged synchronous execution blocks the main event loop entirely.\n\nWhile the event loop is blocked, the server cannot process any other incoming requests, network events, or database callbacks. This behavior transforms a standard string-parsing routine into an effective unauthenticated remote denial of service vector. The system remains completely unresponsive until the computation terminates or the node process is terminated by resource exhaustion limits.
The root cause is divided into three distinct architectural bottlenecks where algorithmic operations scale quadratically ($O(n^2)$) relative to the size of the input.\n\nThe first bottleneck is located in lib/addressparser/index.js during the accumulation of parsed addresses. The parser iterates over parsed address collections and combines them using Array.prototype.concat(). In JavaScript, concat() allocates a new array and copies all existing and new elements to it. Iterating this operation $N$ times over $N$ addresses results in a cumulative copying overhead of $O(n^2)$ complexity.\n\nThe second bottleneck exists within the display-name fragment recombination logic. To resolve unquoted display names containing commas, the parser iterates backward and executes Array.prototype.splice(i, 1) to merge fragments in place. Splicing elements shifts all subsequent elements in the array to fill the gap. For highly fragmented inputs, executing this operation recursively degrades performance quadratically.\n\nThe third bottleneck resides inside the mime-node envelope generator's _convertAddresses() function. To ensure unique recipients, the code executes Array.prototype.some() to scan the output list for existing records. Performing a linear array scan for every new address elements means that processing a list of $N$ recipients requires $O(n^2)$ comparisons. If these recipients are spread across multiple headers, the validation state is continuously rebuilt, exacerbating the performance penalty.
The following architectural flow illustrates how input processing escalates from a string parser to a blocked event loop:\n\nmermaid\ngraph LR\n A["Untrusted Address Input"] --> B["addressparser() Validation"]\n B --> C["Loop with Array.concat()"]\n B --> D["Loop with Array.splice()"]\n C --> E["O(N^2) Accumulation"]\n D --> E\n E --> F["MimeNode._convertAddresses()"]\n F --> G["Linear Array.some() Scan"]\n G --> H["O(N^2) Envelope Deduplication"]\n H --> I["Node.js Event Loop Blocked"]\n\n\nThis diagram outlines the sequential parsing steps and the precise points where the $O(n^2)$ complexity bottlenecks occur inside both addressparser and mime-node.
The vulnerabilities were remediated across three distinct performance-focused commits on August 31, 2026. The patches replace quadratic operations with linear alternatives.\n\nCommit 9116da9528c6524cefaed75185602a7e85d20434 resolves the address parser overhead. It replaces the concat accumulation with an in-place .push() loop, lowering allocation overhead to $O(1)$ per element. It also replaces the in-place splice with a single-pass merge accumulator array that is reversed at the end of the execution block:\n\njavascript\n// Vulnerable logic used concat and splice:\n// parsedAddresses = parsedAddresses.concat(handled);\n// parsedAddresses.splice(i, 1);\n\n// Patched logic uses pushing and a reverse operation:\nfor (let i = 0; i < handled.length; i++) {\n parsedAddresses.push(handled[i]);\n}\n\nconst mergedAddresses = [];\nfor (let i = parsedAddresses.length - 1; i >= 0; i--) {\n const current = parsedAddresses[i];\n const next = mergedAddresses.length ? mergedAddresses[mergedAddresses.length - 1] : null;\n if (next && current.address === '' && current.name && !current.group && next.address && next.name) {\n next.name = current.name + ', ' + next.name;\n } else {\n mergedAddresses.push(current);\n }\n}\nmergedAddresses.reverse();\nparsedAddresses = mergedAddresses;\n\n\nCommit 7cc38af418ffa6fc7e86085195ca5ca681694b3e addresses the envelope deduplication lookup overhead. The linear lookups via .some() are replaced with $O(1)$ lookups leveraging a native JavaScript Set:\n\njavascript\n// Patched logic utilizes Set for deduplication\nif (!seenAddresses) {\n seenAddresses = new Set();\n for (let i = 0; i < uniqueList.length; i++) {\n seenAddresses.add(uniqueList[i].address);\n }\n}\n\n// ...\nif (!seenAddresses.has(address.address)) {\n seenAddresses.add(address.address);\n uniqueList.push(address);\n}\n\n\nCommit 34da64282dcdc9b0581c721a27ab2fa226673150 fixes cross-header deduplication by instantiating the seenRecipients set once per envelope cycle instead of recreating it on each individual header call. This change ensures that multi-header emails scale linearly. These combined changes reduce the execution time for 100,000 addresses from over 25 seconds down to under 100 milliseconds.
Exploitation of this vulnerability requires no special authentication or privileges. The attack vector depends entirely on the application's ingestion of email address lists from untrusted inputs.\n\nAn attacker can construct a payload consisting of a large, comma-separated string containing redundant email addresses. Alternatively, the attacker can format a payload containing highly fragmented, unquoted names with commas to maximize the display-name splicing execution path. An example payload structure is shown below:\n\ntext\nTo: a, b <c@d.com>, a, b <c@d.com>, ... [repeated 50,000 times]\n\n\nWhen the application processes this payload, the Node.js process experiences 100% CPU utilization. Because Node.js is single-threaded, the entire process is blocked. Any health check endpoints will fail to respond, causing load balancers or container orchestrators to mark the service as unhealthy and terminate the container instance, resulting in service disruption.
The primary impact of this vulnerability is complete and immediate denial of service. Because the main thread is blocked, any active connections to the application are dropped or timed out, and no new connections can be established.\n\nThis vulnerability does not allow for privilege escalation, remote code execution, or information disclosure. The threat is strictly limited to availability, making it a high-impact reliability concern for production workloads handling mail queues or webhook events.\n\nThe CVSS v3.1 vector string is calculated as CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H, reflecting network-based, unauthenticated, low-complexity denial of service with high availability impact.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
nodemailer Nodemailer | < 9.1.0 | 9.1.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-400 |
| Attack Vector | Network (Unauthenticated) |
| CVSS Score | 7.5 |
| EPSS Score | 0.00045 |
| Impact | Denial of Service (DoS) |
| Exploit Status | poc |
| KEV Status | Not Listed |
The system does not properly control the allocation and maintenance of resources, allowing an attacker to trigger quadratic resource consumption.
Nodemailer (prior to version 9.1.0) is vulnerable to an IDN/Punycode domain allow-list bypass due to an interpretation conflict between legacy RFC-3492 codecs and modern UTS-46 Unicode parsers.
A missing authorization vulnerability (CWE-862) exists in n8n where AI Agent workflows executing as tools bypass the Sub-Workflow Caller Policy settings, allowing authenticated users with agent creation privileges to invoke unauthorized sub-workflows across project boundaries.
A Denial of Service (DoS) vulnerability exists in the xmldom library when parsing HTML-mode documents with mixed-case closing tags for raw-text or escapable raw-text elements like script, style, textarea, or title. This leads to algorithmic complexity issues and quadratic output amplification during DOM serialization.
A command-line option injection vulnerability in GitPython allows low-privilege or unauthenticated actors to read arbitrary local files. The flaw resides in the TagReference.create() function, which fails to evaluate positional arguments against the library's unsafe-option denylist, enabling the execution of native git commands with injected option flags.
GitPython prior to version 3.1.59 contains a path traversal vulnerability via parameter injection. The clone denylist did not restrict the `--separate-git-dir` option, allowing attackers to write repository metadata to arbitrary system paths.
CVE-2026-72925 is a critical vulnerability in the SWC HTML minifier (@swc/html and swc_html_minifier) where safe Unicode-escaped characters in embedded JSON script tags are normalized into raw, unescaped characters during optimization, causing browser-side HTML injection and Cross-Site Scripting.