Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@
- fixed `attachmentfs()` in the mail: the file of the storage is read as a Buffer and the message is completed (it threw a `TypeError` and the SMTP connection stayed open)
- fixed `attachment()` with a `Buffer` in the mail: the Base64 of the whole Buffer is split into lines (the bytes were split into pieces of 68 bytes and each one got its own padding)
- fixed `@{hostname()}` view engine command
- security: hardened the hostnames against the Node.js security release of 2026-10-21 (patched in Node.js 22.23.4, 24.22.1 and 26.12.1) ; the framework does not require a specific version of Node.js, the hostnames are normalized and validated in the framework itself
- CVE-2026-105482: `dns.resolve*()` truncates a name at a NUL byte (`attacker.example\0.trusted.example`)
- CVE-2026-105483, CVE-2026-106069, CVE-2026-106072: `tls.checkServerIdentity()` with IPv4 literals written with non-ASCII digits, leading zeros or in a non-canonical form (`0x7f.0.0.1`, `127.1`, `2130706433`)
- added `U.validhost(host)` that accepts only plain hostnames and canonical IP literals
- added `U.tohost(host)` that converts a hostname to the canonical ASCII form (punycode, dotted IPv4) and returns an empty string for an invalid hostname
- fixed `U.resolve()` (used by `REQUEST()`): an invalid hostname gives an error before the DNS query
- fixed `U.connect()`: the `host` is normalized and an invalid hostname gives the `Invalid hostname` error
- fixed the SMTP `server` in the mail: the hostname is normalized (international names to punycode, IPv4 to the canonical form) and an invalid hostname gives the `Invalid SMTP server hostname.` error
- note: the issue CVE-2026-105481 (unsolicited data on a reused keep-alive socket, `http.Agent`) is fixed only by updating Node.js ; the Brotli issue CVE-2026-111497 does not affect the framework (only `gzip` and `deflate` are decompressed)

========================
0.0.19
Expand Down
9 changes: 7 additions & 2 deletions mail.js
Original file line number Diff line number Diff line change
Expand Up @@ -477,8 +477,13 @@ Mailer.send = function(opt, messages, callback) {
if (opt.secure && !opt.port)
opt.port = 465;

if (!opt.server) {
const err = new Error('No SMTP server configuration.');
const server = opt.server ? F.TUtils.tohost(opt.server) : '';

if (server && server !== opt.server)
opt = Object.assign({}, opt, { server: server });

if (!server) {
const err = new Error(opt.server ? 'Invalid SMTP server hostname.' : 'No SMTP server configuration.');
let iscallback = false;

if (callback) {
Expand Down
38 changes: 38 additions & 0 deletions utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -489,12 +489,41 @@ exports.toURLEncode = function(value) {
return builder.length ? builder.join('&') : '';
};

const REG_HOST_NUMERIC = /^(0x[0-9a-f]+|[0-9]+)(\.(0x[0-9a-f]+|[0-9]+))*$/i;
const REG_HOST_IPV4 = /^((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])$/;

// Accepts plain hostnames and canonical IP literals only. It rejects NUL bytes, non-ASCII characters
// and non-canonical IPv4 forms (0127.0.0.1, 0x7f.1, 2130706433, 127.1) which Node.js, certificate
// checks and the OS can interpret differently.
exports.validhost = function(host) {

if (!host || typeof(host) !== 'string' || /[^\x21-\x7e]/.test(host))
return false;

return REG_HOST_NUMERIC.test(host) ? REG_HOST_IPV4.test(host) : true;
};

// Converts a host to its canonical ASCII form (punycode for international names, canonical dotted IPv4)
// and returns an empty string when the host is not valid.
exports.tohost = function(host) {

if (!host || typeof(host) !== 'string')
return '';

if (host.indexOf(':') === -1)
host = F.Url.domainToASCII(host);

return exports.validhost(host) ? host : '';
};

exports.resolve = function(url, callback, param) {

let uri;

try {
uri = exports.parseURI(url);
if (!exports.validhost(uri.hostname))
throw new Error('Invalid hostname');
} catch (e) {
callback(e);
return;
Expand Down Expand Up @@ -5921,6 +5950,15 @@ exports.connect = function(opt, callback) {

delete opt.tls;

if (opt.host) {
opt.host = exports.tohost(opt.host);
if (!opt.host) {
meta.destroy = meta.close = NOOP;
callback && setImmediate(callback, new Error('Invalid hostname'));
return meta;
}
}

var close = function() {

if (meta.socket1) {
Expand Down
Loading