nodemailer 8.0.8 → 8.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/lib/mailer/index.js +32 -24
- package/lib/mailer/mail-message.js +25 -19
- package/lib/shared/index.js +24 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## [8.0.9](https://github.com/nodemailer/nodemailer/compare/v8.0.8...v8.0.9) (2026-05-26)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* two pending security advisories (jsonTransport access bypass, List-* CRLF injection) ([#1820](https://github.com/nodemailer/nodemailer/issues/1820)) ([5f69497](https://github.com/nodemailer/nodemailer/commit/5f694977da2e0e13dc947037566e8e689a01217e))
|
|
9
|
+
|
|
3
10
|
## [8.0.8](https://github.com/nodemailer/nodemailer/compare/v8.0.7...v8.0.8) (2026-05-23)
|
|
4
11
|
|
|
5
12
|
|
package/lib/mailer/index.js
CHANGED
|
@@ -407,31 +407,39 @@ class Mail extends EventEmitter {
|
|
|
407
407
|
if ((!this.options.attachDataUrls && !mail.data.attachDataUrls) || !mail.data.html) {
|
|
408
408
|
return callback();
|
|
409
409
|
}
|
|
410
|
-
mail.resolveContent(
|
|
411
|
-
|
|
412
|
-
|
|
410
|
+
mail.resolveContent(
|
|
411
|
+
mail.data,
|
|
412
|
+
'html',
|
|
413
|
+
{ disableFileAccess: mail.data.disableFileAccess, disableUrlAccess: mail.data.disableUrlAccess },
|
|
414
|
+
(err, html) => {
|
|
415
|
+
if (err) {
|
|
416
|
+
return callback(err);
|
|
417
|
+
}
|
|
418
|
+
let cidCounter = 0;
|
|
419
|
+
html = (html || '')
|
|
420
|
+
.toString()
|
|
421
|
+
.replace(
|
|
422
|
+
/(<img\b[^<>]{0,1024} src\s{0,20}=[\s"']{0,20})(data:([^;]+);[^"'>\s]+)/gi,
|
|
423
|
+
(match, prefix, dataUri, mimeType) => {
|
|
424
|
+
const cid = crypto.randomBytes(10).toString('hex') + '@localhost';
|
|
425
|
+
if (!mail.data.attachments) {
|
|
426
|
+
mail.data.attachments = [];
|
|
427
|
+
}
|
|
428
|
+
if (!Array.isArray(mail.data.attachments)) {
|
|
429
|
+
mail.data.attachments = [].concat(mail.data.attachments || []);
|
|
430
|
+
}
|
|
431
|
+
mail.data.attachments.push({
|
|
432
|
+
path: dataUri,
|
|
433
|
+
cid,
|
|
434
|
+
filename: 'image-' + ++cidCounter + '.' + mimeTypes.detectExtension(mimeType)
|
|
435
|
+
});
|
|
436
|
+
return prefix + 'cid:' + cid;
|
|
437
|
+
}
|
|
438
|
+
);
|
|
439
|
+
mail.data.html = html;
|
|
440
|
+
callback();
|
|
413
441
|
}
|
|
414
|
-
|
|
415
|
-
html = (html || '')
|
|
416
|
-
.toString()
|
|
417
|
-
.replace(/(<img\b[^<>]{0,1024} src\s{0,20}=[\s"']{0,20})(data:([^;]+);[^"'>\s]+)/gi, (match, prefix, dataUri, mimeType) => {
|
|
418
|
-
const cid = crypto.randomBytes(10).toString('hex') + '@localhost';
|
|
419
|
-
if (!mail.data.attachments) {
|
|
420
|
-
mail.data.attachments = [];
|
|
421
|
-
}
|
|
422
|
-
if (!Array.isArray(mail.data.attachments)) {
|
|
423
|
-
mail.data.attachments = [].concat(mail.data.attachments || []);
|
|
424
|
-
}
|
|
425
|
-
mail.data.attachments.push({
|
|
426
|
-
path: dataUri,
|
|
427
|
-
cid,
|
|
428
|
-
filename: 'image-' + ++cidCounter + '.' + mimeTypes.detectExtension(mimeType)
|
|
429
|
-
});
|
|
430
|
-
return prefix + 'cid:' + cid;
|
|
431
|
-
});
|
|
432
|
-
mail.data.html = html;
|
|
433
|
-
callback();
|
|
434
|
-
});
|
|
442
|
+
);
|
|
435
443
|
}
|
|
436
444
|
|
|
437
445
|
set(key, value) {
|
|
@@ -111,25 +111,29 @@ class MailMessage {
|
|
|
111
111
|
if (!args[0] || !args[0][args[1]]) {
|
|
112
112
|
return resolveNext();
|
|
113
113
|
}
|
|
114
|
-
shared.resolveContent(
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
114
|
+
shared.resolveContent(
|
|
115
|
+
...args,
|
|
116
|
+
{ disableFileAccess: this.data.disableFileAccess, disableUrlAccess: this.data.disableUrlAccess },
|
|
117
|
+
(err, value) => {
|
|
118
|
+
if (err) {
|
|
119
|
+
return callback(err);
|
|
120
|
+
}
|
|
118
121
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
122
|
+
const node = {
|
|
123
|
+
content: value
|
|
124
|
+
};
|
|
125
|
+
if (args[0][args[1]] && typeof args[0][args[1]] === 'object' && !Buffer.isBuffer(args[0][args[1]])) {
|
|
126
|
+
Object.keys(args[0][args[1]]).forEach(key => {
|
|
127
|
+
if (!(key in node) && !['content', 'path', 'href', 'raw'].includes(key)) {
|
|
128
|
+
node[key] = args[0][args[1]][key];
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
}
|
|
129
132
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
+
args[0][args[1]] = node;
|
|
134
|
+
resolveNext();
|
|
135
|
+
}
|
|
136
|
+
);
|
|
133
137
|
};
|
|
134
138
|
|
|
135
139
|
setImmediate(() => resolveNext());
|
|
@@ -269,7 +273,8 @@ class MailMessage {
|
|
|
269
273
|
if (value && value.url) {
|
|
270
274
|
if (key.toLowerCase().trim() === 'id') {
|
|
271
275
|
// List-ID: "comment" <domain>
|
|
272
|
-
|
|
276
|
+
// strip CR/LF so a comment can't inject extra header lines
|
|
277
|
+
let comment = (value.comment || '').toString().replace(/\r?\n|\r/g, ' ');
|
|
273
278
|
if (mimeFuncs.isPlainText(comment)) {
|
|
274
279
|
comment = '"' + comment + '"';
|
|
275
280
|
} else {
|
|
@@ -280,7 +285,8 @@ class MailMessage {
|
|
|
280
285
|
}
|
|
281
286
|
|
|
282
287
|
// List-*: <http://domain> (comment)
|
|
283
|
-
|
|
288
|
+
// strip CR/LF so a comment can't inject extra header lines
|
|
289
|
+
let comment = (value.comment || '').toString().replace(/\r?\n|\r/g, ' ');
|
|
284
290
|
if (!mimeFuncs.isPlainText(comment)) {
|
|
285
291
|
comment = mimeFuncs.encodeWord(comment);
|
|
286
292
|
}
|
package/lib/shared/index.js
CHANGED
|
@@ -6,6 +6,7 @@ const urllib = require('url');
|
|
|
6
6
|
const util = require('util');
|
|
7
7
|
const fs = require('fs');
|
|
8
8
|
const nmfetch = require('../fetch');
|
|
9
|
+
const errors = require('../errors');
|
|
9
10
|
const dns = require('dns');
|
|
10
11
|
const net = require('net');
|
|
11
12
|
const os = require('os');
|
|
@@ -501,9 +502,17 @@ module.exports.parseDataURI = uri => {
|
|
|
501
502
|
*
|
|
502
503
|
* @param {Object} data An object or an Array you want to resolve an element for
|
|
503
504
|
* @param {String|Number} key Property name or an Array index
|
|
505
|
+
* @param {Object} [options] Optional access policy: { disableFileAccess, disableUrlAccess }
|
|
504
506
|
* @param {Function} callback Callback function with (err, value)
|
|
505
507
|
*/
|
|
506
|
-
module.exports.resolveContent = (data, key, callback) => {
|
|
508
|
+
module.exports.resolveContent = (data, key, options, callback) => {
|
|
509
|
+
// options is optional; support the legacy resolveContent(data, key, callback) signature
|
|
510
|
+
if (!callback && typeof options === 'function') {
|
|
511
|
+
callback = options;
|
|
512
|
+
options = false;
|
|
513
|
+
}
|
|
514
|
+
options = options || {};
|
|
515
|
+
|
|
507
516
|
let promise;
|
|
508
517
|
|
|
509
518
|
if (!callback) {
|
|
@@ -538,6 +547,13 @@ module.exports.resolveContent = (data, key, callback) => {
|
|
|
538
547
|
callback(null, value);
|
|
539
548
|
});
|
|
540
549
|
} else if (/^https?:\/\//i.test(content.path || content.href)) {
|
|
550
|
+
if (options.disableUrlAccess) {
|
|
551
|
+
return setImmediate(() => {
|
|
552
|
+
const err = new Error('Url access rejected for ' + (content.path || content.href));
|
|
553
|
+
err.code = errors.EURLACCESS;
|
|
554
|
+
callback(err);
|
|
555
|
+
});
|
|
556
|
+
}
|
|
541
557
|
return resolveStream(nmfetch(content.path || content.href), callback);
|
|
542
558
|
} else if (/^data:/i.test(content.path || content.href)) {
|
|
543
559
|
const parsedDataUri = module.exports.parseDataURI(content.path || content.href);
|
|
@@ -547,6 +563,13 @@ module.exports.resolveContent = (data, key, callback) => {
|
|
|
547
563
|
}
|
|
548
564
|
return callback(null, parsedDataUri.data);
|
|
549
565
|
} else if (content.path) {
|
|
566
|
+
if (options.disableFileAccess) {
|
|
567
|
+
return setImmediate(() => {
|
|
568
|
+
const err = new Error('File access rejected for ' + content.path);
|
|
569
|
+
err.code = errors.EFILEACCESS;
|
|
570
|
+
callback(err);
|
|
571
|
+
});
|
|
572
|
+
}
|
|
550
573
|
return resolveStream(fs.createReadStream(content.path), callback);
|
|
551
574
|
}
|
|
552
575
|
}
|