Sep 9, 2026·5 min read·30 visits
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.
A Time-of-Check Time-of-Use (TOCTOU) race condition in the Python `filelock` library (versions 4.1.0 to 4.2.0) allows local unprivileged attackers to cause arbitrary directory deletion by swapping file system targets during lock cleanup or dead owner sweeps.
A local race condition and mutual exclusion bypass vulnerability exists in filelock versions 4.1.0 through 4.2.0. On Linux filesystems with rapid inode reuse, a local attacker can manipulate modification timestamps and lock files to cause waiting processes to falsely evict active soft lock markers.
vLLM prior to version 0.31.0 contains an uncontrolled resource consumption vulnerability (CWE-400) in its HTTP API request handling logic. Unauthenticated remote attackers can transmit per-request keyword arguments (`mm_processor_kwargs` and `media_io_kwargs`) that are passed directly into internal multimodal preprocessing pipelines, causing CPU/GPU memory exhaustion and crashing the engine process.
An insufficient verification of data authenticity vulnerability in SiYuan prior to version 3.7.4 allows an attacker to overwrite arbitrary installed marketplace packages (plugins, widgets, themes, and templates). By providing a mismatched package name and package archive repository URL during a Bazaar installation request, arbitrary file write into trusted plugin directories can be achieved, enabling persistence and execution across application restarts.
In obsidian-tc versions 1.0.1 through 1.31.3, memory lifecycle tools fail to validate note ownership prior to rematerializing Markdown files and execute non-atomic SQLite database mutations. An authorized caller can overwrite arbitrary note bodies on disk and cause persistent desynchronization between the database memory graph and physical vault files.
A high-severity authorization bypass vulnerability in obsidian-tc allows clients to bypass Human-In-The-Loop (HITL) confirmation prompts over Streamable HTTP deployments. Due to incomplete validation of returned prompt responses, an attacker echoing a signed requestState token can force execution of destructive vault operations even if a human user declines or cancels the prompt.