Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
📦
📦 npm
Not in CISA KEV
MEDIUM severity

Nodemailer: resolveContent() on a MailMessage bypasses disableFileAccess/disableUrlAccess when called with the legacy signatureGHSA-8m3c-c648-2xjj

MEDIUMFix: nodemailer/nodemailer@ab7ef34

GHSA-8m3c-c648-2xjj is a medium-severity (CVSS 5.9) CWE-73 vulnerability in nodemailer. A fix is available for nodemailer — see the affected versions and patch details below.

Also known asCVE-2026-92595
Published
Updated
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Oct 22, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

Proof-of-concept exploit code exists

  • CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.

Exploitation and automatability from CISA’s SSVC triage for GHSA-8m3c-c648-2xjj.

EPSS Exploitation Probability

via FIRST.org ↗
0.3%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs21th percentile — riskier than 21% of all scored CVEsHighest risk
0.00%0.27%0.53%0.80%0.3%0.3%Oct 26Oct 26

Probability of exploitation in the next 30 days, from FIRST.org EPSS.

How urgent is this, really

GHSA-8m3c-c648-2xjj by exploitation likelihood (EPSS) against impact (CVSS). Outside the shaded patch-first corner.

Where this sits among everything scored

Of 395,249 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Counts from FIRST.org, log-scaled.

Real-World Exposure

1 pkg affected

How broadly this vulnerability is actually deployed: weekly install volume shows current usage, and reverse-dependency count shows how many other packages break if it stays unpatched.

11Kother npm packages depend on this — each one inherits the vulnerability until it's patched upstream
nodemailernpm
15.6Mdownloads / week

Description

Summary

Nodemailer's disableFileAccess / disableUrlAccess options are a security sandbox that lets an application forbid untrusted message content (html/text/attachment path/href) from reading local files or making outbound HTTP(S) requests. The fix for GHSA-wqvq-jvpq-h66f (commit 5f69497) threaded these flags through the library's internal resolution paths (MailMessage.resolveAll() and _convertDataImages()), but the public plugin API MailMessage.resolveContent(...args) (lib/mailer/mail-message.js:41-43) remains a raw passthrough to shared.resolveContent().

When called with the documented legacy signature mail.resolveContent(data, key, callback), shared.resolveContent normalizes the missing options argument to an empty object (options = options || {}, lib/shared/index.js:530). The message-level flags that the MailMessage constructor already copied into mail.data (lib/mailer/mail-message.js:34-38) are silently discarded, so resolveContentValue skips both access-control guards and reaches nmfetch(url) (SSRF, lib/shared/index.js:588) or fs.createReadStream(path) (arbitrary file read, lib/shared/index.js:597).

A plugin or application code that resolves message content through the documented API (the same API the library's own _convertDataImages uses, threading the flags explicitly) thereby bypasses the sandbox an application deliberately enabled.

Details

Root cause. The MailMessage constructor stores the transporter-level sandbox flags on the message object (lib/mailer/mail-message.js:34-38):

['disableFileAccess', 'disableUrlAccess', 'normalizeHeaderKey', 'maxRecipients'].forEach(key => {
    if (key in options) {
        this.data[key] = options[key];
    }
});

The public resolver is a pure passthrough (lib/mailer/mail-message.js:41-43):

resolveContent(...args) {
    return shared.resolveContent(...args);
}

shared.resolveContent supports the legacy 3-argument signature and collapses the missing options to {} (lib/shared/index.js:524-530):

module.exports.resolveContent = (data, key, options, callback) => {
    // options is optional; support the legacy resolveContent(data, key, callback) signature
    if (!callback && typeof options === 'function') {
        callback = options;
        options = false;
    }
    options = options || {};
    ...
    resolveContentValue(data, key, options, callback);

resolveContentValue then checks options.disableUrlAccess / options.disableFileAccess (lib/shared/index.js:581 / :590), both undefined for the legacy signature, so it falls through to nmfetch (:588) or fs.createReadStream (:597).

Contrast with the fixed paths. resolveAll() (lib/mailer/mail-message.js:112-115) and _convertDataImages() (lib/mailer/index.js:437-440) both pass the message flags explicitly. The MIME streaming path (lib/mime-node/index.js:1059-1077) also honors the flags. So an application that enables the sandbox and then calls transporter.sendMail() is protected; the bypass appears only when message content is resolved through the public legacy-signature API — which is the documented plugin usage (the resolveContent JSDoc at lib/shared/index.js:510-523 states it is "useful when you want to create a plugin that needs a content value").

Affected versions. Confirmed on 9.1.0 (HEAD efd6e29c10c6e0c25c57bd2f2a71302838235a4f, the current npm latest). The gap was introduced by the GHSA-wqvq-jvpq-h66f fix and is still present; the public API has no regression coverage (test/mailer/mail-message-test.js contains no resolveContent test).

PoC

Requires: [email protected], a readable local file, and any reachable HTTP endpoint (loopback suffices). Non-destructive; no network egress beyond a local listener.

'use strict';
const nodemailer = require('nodemailer');
const MailMessage = require('nodemailer/lib/mailer/mail-message');

const TARGET_FILE = '/app/src/package.json';   // any readable local file
const SSRF_URL = 'http://http-sink:8080/poc-ssrf'; // any local/internal HTTP target

const transporter = nodemailer.createTransport({
    streamTransport: true,
    disableFileAccess: true,   // sandbox explicitly enabled
    disableUrlAccess: true
});

const data = {
    from: '[email protected]', to: '[email protected]', subject: 'poc', text: 'hello',
    html: { path: TARGET_FILE },
    attachments: [{ filename: 'x.bin', href: SSRF_URL }]
};
const mail = new MailMessage(transporter, data);
// mail.data.disableFileAccess === true, mail.data.disableUrlAccess === true

// Documented legacy plugin signature — options argument omitted:
mail.resolveContent(mail.data, 'html', (err, value) => {
    if (err) return console.log('BLOCKED', err.code);
    console.log('FILE_READ_OK len=', value.length);          // -> 1647 (package.json)
});
mail.resolveContent(mail.data.attachments, 0, (err, body) => {
    if (err) return console.log('BLOCKED', err.code);
    console.log('URL_FETCH_OK body=', body.toString());      // -> fetched response
});

Observed output on the audit environment (Node 22, [email protected]):

mail.data.disableFileAccess = true | disableUrlAccess = true
[CONTROL resolveAll] err = EFILEACCESS : File access rejected for /app/src/package.json
[CONTROL html.path explicit-options] err = EFILEACCESS
[BYPASS html.path legacy] READ OK len = 1647 head = "{\n    \"name\": \"nodemailer\",\n    \"version\": \"9.1.0\",\n    \"des"
[BYPASS att[0].href legacy] FETCH OK len = 13 body = "HTTP-SINK OK\n"

The negative controls (resolveAll, and resolveContent with explicit { disableFileAccess: true }) return EFILEACCESS, proving the sandbox works on the protected paths and only the legacy-signature passthrough is bypassed. The same bypass reproduces inside a real transporter.sendMail() flow when a compile plugin calls mail.resolveContent(mail.data, 'html', cb) / mail.resolveContent(mail.data.attachments, 0, cb).

Impact

An application that enables disableFileAccess / disableUrlAccess to contain untrusted message content and that resolves content through the documented plugin API (mail.resolveContent(data, key, callback)) has its sandbox silently bypassed:

  • Arbitrary local file disclosure: a message html/attachment path pointing at a server file (/etc/passwd, .env, key material) is read and returned to the caller / delivered in the message.
  • Server-side request forgery: a message href pointing at an internal or loopback URL is fetched from the application host.

Reachability precondition: the sandbox flags must be enabled (default off) and the application or its plugin must invoke the documented legacy-signature API on attacker-influenced data. The default transporter.sendMail() path remains protected, so this is a defense-in-depth gap in the library's own access-control enforcement rather than a default-flow bypass. It is the same vulnerability class as the previously accepted GHSA-wqvq-jvpq-h66f (CVE-2026-82660) and GHSA-p6gq-j5cr-w38f (CVE-2026-82659), on a distinct third code path.

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
📦npmnodemailerall versions9.1.1npm install nodemailer@9.1.1

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for nodemailer, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update nodemailer to 9.1.1 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-8m3c-c648-2xjj is resolved across your whole dependency graph.

  3. Workarounds

    Restrict outbound requests from the affected component to an allowlist of hosts, block access to link-local and internal address ranges at the network layer, and require authentication on internal services so a forged request cannot reach them unauthenticated.

Fixing This On Your OS

If you run this on a Linux distribution, patch through your package manager against the distro's own security advisory below — it tracks the exact backported fix for your release, which can ship on a different timeline (and sometimes a different severity) than the upstream project.

Red HatModerate

This Moderate flaw in Nodemailer allows information disclosure and server-side request forgery (SSRF) in Red Hat products that utilize the `nodemailer` package. The vulnerability arises when applications process untrusted message content via the legacy three-argument `MailMessage.resolveContent()` API, bypassing…

Workaround published by Red Hat
The vulnerability in Nodemailer is dependent on specific application code usage of a legacy API signature and requires the sandbox flags to be explicitly enabled. Mitigation for this issue is either not available or the currently available options do not meet the Red Hat Product Security criteria comprising ease of use and deployment, applicability to widespread installation base, or stability.
Source: Red Hat security advisory for GHSA-8m3c-c648-2xjj (CC BY 4.0)

Frequently Asked Questions

### Summary Nodemailer's `disableFileAccess` / `disableUrlAccess` options are a security sandbox that lets an application forbid untrusted message content (`html`/`text`/attachment `path`/`href`) from reading local files or making outbound HTTP(S) requests. The fix for GHSA-wqvq-jvpq-h66f (commit `5f69497`) threaded these flags through the library's internal resolution paths (`MailMessage.resolveAll()` and `_convertDataImages()`), but the public plugin API `MailMessage.resolveContent(...args)` (`lib/mailer/mail-message.js:41-43`) remains a raw passthrough to `shared.resolveContent()`. When c
O3 Security · Impact-Aware SCA

Is GHSA-8m3c-c648-2xjj in your dependencies?

Find it across npm, including transitive dependencies.

Nodemailer: resolveContent() on a MailMessage bypasses…