GitHub Advisoryadvisory · npm
GHSA-wqvq-jvpq-h66f Nodemailer jsonTransport bypasses disableFileAccess and disableUrlAccess during message normalization
MEDIUM
Official page ↗
- Ecosystem
- npm
- Packages
- nodemailer
- Severity
- Moderate
- CVSS vector
- CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N
- Weaknesses
- CWE-862
- Reviewed
- 2026-06-15
Affected packages
• npm nodemailer: < 8.0.9 → fixed in 8.0.9
Description
Summary
Nodemailer's `disableFileAccess` and `disableUrlAccess` options are intended to prevent message content and attachments from reading local files or fetching URLs. The normal MIME streaming path enforces those options in `MimeNode._getStream()`. However, `jsonTransport` serializes messages by calling `mail.normalize()`, which resolves `html`, `text`, alternatives, calendar events, and attachments through `shared.resolveContent()` before MIME generation. `shared.resolveContent()` reads local files and fetches HTTP(S) URLs directly, without receiving or checking `disableFileAccess` or `disableUrlAccess`.
As a result, applications that use `jsonTransport` as a safe serializer or queue payload generator while relying on `disableFileAccess` / `disableUrlAccess` can still be made to read local files into the generated JSON output or make outbound HTTP requests when an attacker controls message content fields such as attachment `path` or `text.href`.
The same missing-enforcement root cause is also reachable before normal streaming when `attachDataUrls` causes `_convertDataImages()` to call `mail.resolveContent(mail.data, 'html', ...)`; this should be fixed with the same access-control check.
Details
Source-to-sink evidence:
• `lib/nodemailer.js:42-45` selects `JSONTransport` when `createTransport({ jsonTransport: true, ... })` is used.
• `lib/mailer/mail-message.js:34-39` copies transport-level `disableFileAccess` and `disableUrlAccess` options into `mail.data`.
• `lib/json-transport/index.js:52-76` serializes mail by calling `mail.normalize((err, data) => ...)`.
• `lib/mailer/mail-message.js:46-135` implements `resolveAll()` and calls `shared.resolveContent(...args, ...)` for `html`, `text`, `watchHtml`, `amp`, `icalEvent`, alternatives, and attachments.
• `lib/shared/index.js:506-562` implements `resolveContent()`.
• `lib/shared/index.js:540-541` fetches HTTP(S) content with `nmfetch(content.path || content.href)`.
• `lib/shared/index.js:549-550` reads local files with `fs.createReadStream(content.path)`.
• `shared.resolveContent()` does not check `disableFileAccess` or `disableUrlAccess` and does not receive those flags.
Control path showing intended enforcement:
• `lib/mail-composer/index.js:358-359`, `lib/mail-composer/index.js:367-368`, and sibling child-node creation paths pass `disableUrlAccess` and `disableFileAccess` into `MimeNode`.
• `lib/mime-node/index.js:51-52` stores those flags.
• `lib/mime-node/index.js:984-995` rejects file paths with `EFILEACCESS` when `disableFileAccess` is set.
• `lib/mime-node/index.js:998-1009` rejects URLs with `EURLACCESS` when `disableUrlAccess` is set.
• `test/mail-composer/mail-composer-test.js:1028-1044` includes a normal MIME-streaming test that expects file access to be blocked when `disableFileAccess: true`.
Additional same-root-cause variant:
• `lib/mailer/index.js:406-434` implements `_convertDataImages()` for `attachDataUrls`.
• `lib/mailer/index.js:407-410` calls `mail.resolveContent(mail.data, 'html', ...)` when `attachDataUrls` is enabled and `mail.data.html` is present.
• Because `mail.resolveContent()` delegates to `shared.resolveContent()` at `lib/mailer/mail-message.js:42-44`, an object-form `html: { path: ... }` or `html: { href: ... }` can be resolved before the later MIME streaming enforcement sees the content.
• This variant requires `attachDataUrls` to be enabled, so the main reportable default/common path is `jsonTransport`; both should be fixed by enforcing access flags inside the pre-resolution helper or passing policy into it.
Default/common exposure evidence:
• `jsonTransport` is a shipped runtime transport selected by public `createTransport` options.
• `test/json-transport/json-transport-test.js:9-83` demonstrates that `jsonTransport` intentionally resolves file-backed `html` and attachments into JSON output.
• `disableFileAccess` and `disableUrlAccess` are documented by code and tests as security controls and are copied from transport options into message data for all transports.
• The bypass does not require test-only code, external infrastructure, unsupported configuration, or maintainer-only APIs.
False-positive screening and negative controls:
• The local PoC used the same `disableFileAccess: true` and `disableUrlAccess: true` transport options for both `jsonTransport` and normal `streamTransport` controls.
• `jsonTransport` read the temporary local fixture file and embedded the content in JSON despite `disableFileAccess: true`.
• `streamTransport` with the same attachment and `disableFileAccess: true` rejected with `EFILEACCESS`.
• `jsonTransport` fetched a local HTTP listener despite `disableUrlAccess: true`.
• `streamTransport` with the same URL and `disableUrlAccess: true` rejected with `EURLACCESS`.
• The local URL proof used only `127.0.0.1` and did not contact external infrastructure.
Affected version evidence and uncertainty:
• Confirmed vulnerable: `nodemailer` 8.0.8 at commit `15138a84c543c20aa399218534cdbbfa2ea1ce55`.
• Git history shows `jsonTransport` has existed since commit `d78b63b` (2017-02-09, "Added test for json transport"), and `disableFileAccess` appears in historical setup commit `6218b8d` (2017-01-31), but older versions were not dynamically tested during this audit.
• Affected range is therefore recorded as unknown beyond the confirmed current version.
• No patched version was identified in this checkout.
Severity rationale:
• AV: The vulnerable library path is typically reached through an application-level message submission or rendering/queueing feature.
• AC: A single message field using `path` or `href` triggers the bypass when `jsonTransport` is used.
• PR: Conservative assumption that the attacker is a lower-privileged user of an application that accepts partially user-controlled message objects. Some deployments may expose this unauthenticated, but that was not assumed.
• UI: No user interaction is required after the application accepts the message object.
• S: The impact remains in the embedding application/library security scope.
• C: Local file contents can be copied into the generated JSON output when the application later stores, logs, returns, or forwards that JSON.
• I: The attacker can induce outbound HTTP requests to attacker-chosen or internal URLs from the application host when URL access was intended to be disabled.
• A: No availability impact was demonstrated; the PoC used bounded local files and a localhost listener only.
Final self-review:
• Reproduction evidence was generated locally from this checkout using only a temporary file under the OS temp directory and a local `127.0.0.1` HTTP listener.
• The PoC included positive proof for file read and URL fetch, plus negative controls showing normal `streamTransport` rejects the same inputs with `EFILEACCESS` and `EURLACCESS`.
• The proof is non-destructive, performs no external network traffic, and deletes its temporary fixture.
• Reachability, package exposure, policy-enforcement bypass, same-root-cause variant, and false-positive controls were checked as described above.
• The affected range is not overclaimed; only the current tested version is confirmed vulnerable.
PoC
From a clean checkout of `nodemailer` at commit `15138a84c543c20aa399218534cdbbfa2ea1ce55`, run:
```bash
node transport.sendMail(data, (err, info) => err ? reject(err) : resolve(info)));
}
(async () => {
const jsonTransport = nodemailer.createTransport({ jsonTransport: true, disableFileAccess: true, disableUrlAccess: true });
const jsonInfo = await sendMail(jsonTransport, {…
Related
Weakness · CWE (1)
- CWE-862 Missing Authorization weakness