---
id: "GHSA-4ffr-jq9g-5ffx"
kind: "vulnerability"
aliases: ["GHSA-4ffr-jq9g-5ffx"]
title: "Nodemailer: Quadratic backtracking in SMTP AUTH mechanism detection regexes allows pre-auth remote denial of service"
severity: "medium"
cvss: 5.9
epss: null
exploited: false
status: "open"
category: "package"
products: [{"name": "nodemailer", "fixed": ["10.0.13"], "affected": ">= 3.0.0, <= 10.0.12", "ecosystem": "npm"}]
ecosystems: ["npm"]
tags: ["cwe-1333", "multi-tenant-mailers", "node.js", "nodemailer", "package", "smtp"]
not_affected_if: ["allowlisted-smtp-hosts", "nodemailer-10.0.13-or-later"]
published_at: "2026-10-27T15:04:39+00:00"
first_seen_at: "2026-10-27T15:08:21+00:00"
updated_at: "2026-10-27T15:08:35+00:00"
sources: [{"source": "ghsa", "url": "https://github.com/advisories/GHSA-4ffr-jq9g-5ffx", "first_seen_at": "2026-10-27T15:08:21+00:00"}]
---

# Nodemailer: Quadratic backtracking in SMTP AUTH mechanism detection regexes allows pre-auth remote denial of service

Nodemailer's SMTP client runs four regexes on server EHLO replies that backtrack quadratically when an AUTH line has a long whitespace run and no matching mechanism name. A malicious or man-in-the-middle SMTP server can block the Node.js event loop before authentication, causing denial of service.

_Summary, check and fix written by a model from the sources; labelled as such._

## Affected

- npm:nodemailer affected >= 3.0.0, <= 10.0.12 fixed 10.0.13

## How to check

- Run `npm ls nodemailer` and check whether the resolved version is `10.0.12` or lower, including versions from `3.0.0` onward.
- Search lockfiles (`package-lock.json`, `pnpm-lock.yaml`, `yarn.lock`) for `nodemailer` at a version up to `10.0.12`.
- Check whether the application connects to SMTP hosts that tenants, users or the network can influence.

## Fix

- Upgrade `nodemailer` to `10.0.13` or later.
- Until upgraded, restrict outbound SMTP connections to an allowlist of known hosts.

## Timeline

- 2026-10-27T15:08:21+00:00 ghsa https://github.com/advisories/GHSA-4ffr-jq9g-5ffx

## From the sources

## Summary

Nodemailer's SMTP client applies four regular expressions to a peer's EHLO response to detect the supported SASL mechanisms. Each pattern uses an alternation with two overlapping whitespace-matching sub-patterns, which produces quadratic (O(n^2)) backtracking whenever the mechanism name is absent from the line. A malicious or MITM SMTP server that answers EHLO with `250-AUTH` followed by a long whitespace-only run and a non-matching token blocks the Node.js event loop for tens of seconds to hours during a pre-authentication handshake. No authentication to the target SMTP server is required, and any application that connects to an attacker-influenced host is affected.

## Root Cause

Each of the four AUTH mechanism regexes in `SMTPConnection._actionEHLO` contains the sub-pattern `(?:(\s+|=)[^\n]*\s+|\s+|=)` before the mechanism literal. `[^\n]*` and the trailing `\s+` both match whitespace and can consume the same characters, so when the mechanism literal is absent, the regex engine must try every partition of the whitespace run between `[^\n]*` and `\s+` before failing. The four regexes run unconditionally on every EHLO response, with no length cap, no shape validation, and no early rejection before the engine enters the ambiguous alternation.

```text
Component: nodemailer (npm), SMTP client
Function:  SMTPConnection._actionEHLO
Location:  src/smtp-connection/index.ts, lines 2162, 2167, 2172, 2177
Patterns:  /[ -]AUTH(?:(\s+|=)[^\n]*\s+|\s+|=)PLAIN/i
           /[ -]AUTH(?:(\s+|=)[^\n]*\s+|\s+|=)LOGIN/i
           /[ -]AUTH(?:(\s+|=)[^\n]*\s+|\s+|=)CRAM-MD5/i
           /[ -]AUTH(?:(\s+|=)[^\n]*\s+|\s+|=)XOAUTH2/i
```

## Impact

Algorithmic-complexity denial of service against any Node.js process that opens an SMTP connection to an attacker-controlled or attacker-influenced host. Node.js is single threaded, so the process stalls entirely while the regex engine backtracks: no other requests are served, no timers fire, and the built-in socket timeout (10 minutes) cannot interrupt synchronous JavaScript. A 4,000-character whitespace run inside a single continuation line already blocks each regex for tens of seconds; the client bounds a response at 1 MB, which leaves headroom for blocking runs measured in hours across the four patterns. Two realistic attacker positions reach the sink pre-authentication: a hosted mailer that lets tenants configure their own SMTP server, and any network-adjacent attacker able to man-in-the-middle a cleartext SMTP connection (including before STARTTLS negotiation).

## Proof of Concept

The PoC exercises the exact four regexes copied from `src/smtp-connection/index.ts` against a synthesized `250-AUTH<whitespace>NOMATCH` line, so no live SMTP server is needed to reproduce the timing.

```text
Runtime:      Node.js >= 20
Dependencies: none
Invocation:   node poc.js
```

```javascript
'use strict';

const AUTH_REGEXES = [
    /[ -]AUTH(?:(\s+|=)[^\n]*\s+|\s+|=)PLAIN/i,
    /[ -]AUTH(?:(\s+|=)[^\n]*\s+|\s+|=)LOGIN/i,
    /[ -]AUTH(?:(\s+|=)[^\n]*\s+|\s+|=)CRAM-MD5/i,
    /[ -]AUTH(?:(\s+|=)[^\n]*\s+|\s+|=)XOAUTH2/i,
];

function bench(n) {
    const line = '250-AUTH' + ' '.repeat(n) + 'NOMATCH';
    const start = Date.now();
    for (const re of AUTH_REGEXES) re.test(line);
    return Date.now() - start;
}

for (const n of [500, 1000, 2000, 3000]) {
    console.log(`n=${n}  four-regex time=${bench(n)} ms`);
}

const control = '250-AUTH PLAIN LOGIN CRAM-MD5 XOAUTH2';
const t0 = Date.now();
for (const re of AUTH_REGEXES) re.test(control);
console.log(`well-formed control  four-regex time=${Date.now() - t0} ms`);
```

```text
n=500   four-regex time=914 ms
n=1000  four-regex time=1348 ms
n=2000  four-regex time=10558 ms
n=3000  four-regex time=35251 ms
well-formed control  four-regex time=0 ms
```

Doubling the whitespace run multiplies the wall time by roughly four, which matches the expected O(n^2) growth. The well-formed control shows that a normal EHLO response is parsed in effectively zero time.

## Attack Chain

1. **Exposure:** The victim application, or any dependent that uses `nodemailer` to send mail, opens a TCP or TLS connection to an SMTP host the attacker controls. This happens automatically for multi-tenant mailers whose tenants configure their own outbound SMTP server, and for any client whose plaintext SMTP session can be intercepted by a network-adjacent attacker before STARTTLS negotiation.
2. **Control:** The attacker fully controls the bytes returned in response to `EHLO`. In particular, the attacker chooses the length and content of every `250-` continuation line up to the client's 1 MB response cap.
3. **Path:** Socket bytes are reassembled by `_onData`, decoded by `_processResponse`, and dispatched to `_actionEHLO`. On the first EHLO after the greeting (malicious server with no STARTTLS), on the second EHLO after a successful STARTTLS upgrade, or on the first EHLO when `ignoreTLS` is set, control reaches the AUTH detection block near the end of `_actionEHLO`.
4. **Guard:** The only bounds on this path are `maxResponseSize` (1 MB by default) and the 421 or non-2xx status-code check. Neither prevents O(n^2) backtracking: 4,000 whitespace characters already block each regex for tens of seconds, well below the 1 MB cap. No length, shape, or sanitization check runs before the regexes.
5. **Primitive:** Four `RegExp.prototype.test` calls execute against the crafted line, each entering catastrophic backtracking because `[^\n]*` and `\s+` share the whitespace alphabet and the mechanism literal never matches.
6. **Result:** The Node.js event loop is blocked for the duration of the four regex evaluations. All other work in the process, including timers, unrelated requests, keep-alive traffic, and the SMTP socket timeout itself, is suspended until the regexes return.

## Bypass Evidence

The only response-side protection in this code path is `maxResponseSize`, introduced by the fix for GHSA-4g23-2xm8-66gc (commit `f2d82fa`). That fix targeted quadratic reassembly of long multiline replies inside `_onData` and left the AUTH regex evaluation in `_actionEHLO` untouched. `maxResponseSize` does not prevent this issue: at 4,000 whitespace characters, roughly 0.4 percent of the default 1 MB cap, a single continuation line already blocks each of the four regexes for tens of seconds. No status-code or length gate rejects the payload before the regex engine enters the ambiguous alternation, and Node.js's single-threaded event loop cannot service the socket timeout while a synchronous `test()` call is running. Two other recent linear-time fixes in this project (the addressparser fallback for GHSA-v53p-9fqp-m79j and the DKIM header unfolding for GHSA-39m8-27wv-hr27) address entirely different modules and inputs and do not touch the AUTH mechanism regexes.

## Affected Versions

- **Ecosystem:** `npm`
- **Package:** `nodemailer`
- **Confirmed affected range:** `>= 3.0.0, <= 10.0.12`
- **Latest release checked:** `10.0.12` ([npm registry](https://registry.npmjs.org/nodemailer/10.0.12))
- **Fix status:** 10.0.13

The four AUTH mechanism regexes were introduced when the SMTP client was bundled into `nodemailer` at 3.0.0 (previously a separate `smtp-connection` package). The vulnerable alternation `(?:(\s+|=)[^\n]*\s+|\s+|=)` is present, unchanged, in every published release from 3.0.0 (`lib/smtp-connection/index.js`) through 10.0.12 (`dist/esm/smtp-connection/index.js` and `dist/cjs/smtp-connection/index.js`). A later refactor added a leading `[ -]` character class but did not alter the ambiguous inner alternation. No release, and no development branch inspected at the current HEAD (`30e4cfd`), contains a fix for this pattern.

## Suggested Fix

Replace the four backtracking regexes with a non-ambiguous parse of the AUTH extension. The invariant the fix must enforce is that mechanism detection runs in time linear in the response length regardless of whitespace shape. A minimal approach: match the AUTH extension value 

---

_Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai_
