nodemailer 9.1.0 → 9.1.1

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 CHANGED
@@ -1,5 +1,14 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## [9.1.1](https://github.com/nodemailer/nodemailer/compare/v9.1.0...v9.1.1) (2026-09-01)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **mailer:** apply the message access policy in resolveContent ([dc48ed3](https://github.com/nodemailer/nodemailer/commit/dc48ed395c4d6c79ee5c95eb6eff17bafe391474))
9
+ * **mailer:** keep message data from reopening the access sandbox ([ab7ef34](https://github.com/nodemailer/nodemailer/commit/ab7ef348b9a97b1fd70e7bfbeb56d4ea4a07946b))
10
+ * **mime-node:** inherit the access policy from the tree a node hangs in ([262d550](https://github.com/nodemailer/nodemailer/commit/262d550b1e121e3ff4ef6675d6771a5b2b4ddcec))
11
+
3
12
  ## [9.1.0](https://github.com/nodemailer/nodemailer/compare/v9.0.6...v9.1.0) (2026-08-31)
4
13
 
5
14
 
package/README.md CHANGED
@@ -52,7 +52,7 @@ let configOptions = {
52
52
 
53
53
  #### I have issues with DNS / hosts file
54
54
 
55
- Node.js uses [c-ares](https://nodejs.org/en/docs/meta/topics/dependencies/#c-ares) to resolve domain names, not the DNS library provided by the system, so if you have some custom DNS routing set up, it might be ignored. Nodemailer runs [dns.resolve4()](https://nodejs.org/dist/latest-v16.x/docs/api/dns.html#dnsresolve4hostname-options-callback) and [dns.resolve6()](https://nodejs.org/dist/latest-v16.x/docs/api/dns.html#dnsresolve6hostname-options-callback) to resolve hostname into an IP address. If both calls fail, then Nodemailer will fall back to [dns.lookup()](https://nodejs.org/dist/latest-v16.x/docs/api/dns.html#dnslookuphostname-options-callback). If this does not work for you, you can hard code the IP address into the configuration like shown below. In that case, Nodemailer would not perform any DNS lookups.
55
+ Node.js uses [c-ares](https://github.com/c-ares/c-ares) to resolve domain names, not the DNS library provided by the system, so if you have some custom DNS routing set up, it might be ignored. Nodemailer runs [dns.resolve4()](https://nodejs.org/dist/latest-v16.x/docs/api/dns.html#dnsresolve4hostname-options-callback) and [dns.resolve6()](https://nodejs.org/dist/latest-v16.x/docs/api/dns.html#dnsresolve6hostname-options-callback) to resolve hostname into an IP address. If both calls fail, then Nodemailer will fall back to [dns.lookup()](https://nodejs.org/dist/latest-v16.x/docs/api/dns.html#dnslookuphostname-options-callback). If this does not work for you, you can hard code the IP address into the configuration like shown below. In that case, Nodemailer would not perform any DNS lookups.
56
56
 
57
57
  ```js
58
58
  let configOptions = {
package/SECURITY.md CHANGED
@@ -11,8 +11,8 @@ supported way to receive security updates.
11
11
 
12
12
  | Version | Supported |
13
13
  | ------- | ------------------ |
14
- | 8.x | :white_check_mark: |
15
- | < 8.0 | :x: |
14
+ | 9.x | :white_check_mark: |
15
+ | < 9.0 | :x: |
16
16
 
17
17
  If you are on an older major, please upgrade. See the migration notes at
18
18
  <https://nodemailer.com/> before updating.
@@ -36,10 +36,36 @@ class MailMessage {
36
36
  this.data[key] = options[key];
37
37
  }
38
38
  });
39
+
40
+ // The access flags are a sandbox rather than a message field, so `defaults` counts as
41
+ // transporter configuration for them. For a transporter plugin it is the only channel
42
+ // there is, createTransport leaves `options` undefined for one, and the defaults copy
43
+ // above yields to anything the message already set, which let message data switch the
44
+ // sandbox back off. Closing is one way here, same as in resolveContent below: either
45
+ // side may switch a flag on, neither can switch off what the other closed.
46
+ ['disableFileAccess', 'disableUrlAccess'].forEach(key => {
47
+ if (!(key in options) && hasOwn(defaults, key)) {
48
+ this.data[key] = this.data[key] || defaults[key];
49
+ }
50
+ });
39
51
  }
40
52
 
41
- resolveContent(...args) {
42
- return shared.resolveContent(...args);
53
+ resolveContent(data, key, options, callback) {
54
+ // Most plugins call this with the legacy (data, key, callback) signature, which carries
55
+ // no access policy. The policy belongs to the message, so apply it here. Explicit
56
+ // options may only tighten it, never reopen what the transporter closed.
57
+ if (!callback && typeof options === 'function') {
58
+ callback = options;
59
+ options = false;
60
+ }
61
+ options = options || {};
62
+
63
+ const policy = {
64
+ disableFileAccess: this.data.disableFileAccess || options.disableFileAccess,
65
+ disableUrlAccess: this.data.disableUrlAccess || options.disableUrlAccess
66
+ };
67
+
68
+ return shared.resolveContent(data, key, policy, callback);
43
69
  }
44
70
 
45
71
  resolveAll(callback) {
@@ -244,6 +244,14 @@ class MimeNode {
244
244
  * @return {Object} Appended node object
245
245
  */
246
246
  appendChild(childNode) {
247
+ // Take the node out of the tree it is in first. Leaving it there keeps it in that
248
+ // parent's childNodes, so it still streams as part of the old tree while parentNode
249
+ // already points at the new one, and anything read off the parent chain answers for
250
+ // the wrong tree.
251
+ if (childNode.parentNode && childNode.parentNode !== this) {
252
+ childNode.remove();
253
+ }
254
+
247
255
  if (childNode.rootNode !== this.rootNode) {
248
256
  childNode.rootNode = this.rootNode;
249
257
  childNode._nodeId = ++this.rootNode.nodeCounter;
@@ -1026,6 +1034,26 @@ class MimeNode {
1026
1034
 
1027
1035
  /////// PRIVATE METHODS
1028
1036
 
1037
+ /**
1038
+ * Checks an access policy flag for this node and every node above it. The flags are set
1039
+ * from the options the node was built with, and createChild only ever sees the options
1040
+ * the caller passed, so a child of a closed tree starts out open. Reading the answer off
1041
+ * the parent chain keeps it right whatever order the tree was assembled in.
1042
+ *
1043
+ * @param {String} flag Either 'disableFileAccess' or 'disableUrlAccess'
1044
+ * @return {Boolean} true if this node or an ancestor closed that access
1045
+ */
1046
+ _accessDisabled(flag) {
1047
+ let node = this;
1048
+ while (node) {
1049
+ if (node[flag]) {
1050
+ return true;
1051
+ }
1052
+ node = node.parentNode;
1053
+ }
1054
+ return false;
1055
+ }
1056
+
1029
1057
  /**
1030
1058
  * Detects and returns handle to a stream related with the content.
1031
1059
  *
@@ -1056,7 +1084,7 @@ class MimeNode {
1056
1084
  }
1057
1085
 
1058
1086
  if (content && typeof content.path === 'string' && !content.href) {
1059
- if (this.disableFileAccess) {
1087
+ if (this._accessDisabled('disableFileAccess')) {
1060
1088
  contentStream = new PassThrough();
1061
1089
  setImmediate(() => {
1062
1090
  const err = new Error('File access rejected for ' + content.path);
@@ -1070,7 +1098,7 @@ class MimeNode {
1070
1098
  }
1071
1099
 
1072
1100
  if (content && typeof content.href === 'string') {
1073
- if (this.disableUrlAccess) {
1101
+ if (this._accessDisabled('disableUrlAccess')) {
1074
1102
  contentStream = new PassThrough();
1075
1103
  setImmediate(() => {
1076
1104
  const err = new Error('Url access rejected for ' + content.href);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodemailer",
3
- "version": "9.1.0",
3
+ "version": "9.1.1",
4
4
  "description": "Easy as cake e-mail sending from your Node.js applications",
5
5
  "main": "lib/nodemailer.js",
6
6
  "scripts": {