Skip to content

Bumped @tryghost/nodemailer in ghost/core#27686

Merged
9larsons merged 1 commit intomainfrom
security/nodemailer-bump
May 5, 2026
Merged

Bumped @tryghost/nodemailer in ghost/core#27686
9larsons merged 1 commit intomainfrom
security/nodemailer-bump

Conversation

@9larsons
Copy link
Copy Markdown
Contributor

@9larsons 9larsons commented May 5, 2026

Why

ghost/core was pinning @tryghost/nodemailer@0.3.48, two majors behind the latest published 2.2.0. The 0.3.48 wrapper declares nodemailer: ^6.6.3 and was pulling a transitive nodemailer@6.10.1 alongside ghost/core's direct nodemailer: 8.0.5 — meaning the email-sending wrapper itself was running on a vulnerable nodemailer version regardless of what ghost/core declared directly.

@tryghost/nodemailer@2.2.0 is the current Ghost-team-released version of this package and uses nodemailer@8.0.5 directly. Bumping the wrapper is the right supply-chain answer here — a transitive override on nodemailer would force the same end-state but on top of wrapper code its maintainers never tested with nodemailer 8.

Behavior changes

The wrapper's public API is unchanged (same module.exports = function (transport, options) signature, same supported transport names: smtp, mailgun, sendmail, ses, direct, stub). Two internal mechanics differ:

  1. direct transport now uses nodemailer's built-in {direct: true} option instead of the abandoned nodemailer-direct-transport wrapper. Direct mode is still active and functionally equivalent. The only observable difference is the transporter's name metadata: 'SMTP' (built-in) instead of 'SMTP (direct)' (old wrapper). Four ghost-mailer.test.js assertions updated to match the new name and to additionally verify options.direct === true — the direct-mode test coverage is preserved (and arguably more explicit than the old name-string proxy).

  2. ses transport migrates from @aws-sdk/client-ses (legacy v1: new aws.SES() + SendRawEmail over the v1 API) to @aws-sdk/client-sesv2 (SESv2Client + SendRawEmailCommand). This was an upstream choice in @tryghost/nodemailer@2.2.0 itself. AWS-on-SES users may need to verify their IAM permissions cover the SESv2 endpoints (ses:SendEmail on SESv2 resources rather than the v1 SES resources).

The other transports (smtp, mailgun, sendmail, stub) are unchanged.

Test plan

  • pnpm install — single resolved version of both @tryghost/nodemailer (2.2.0) and nodemailer (8.0.5) in the tree
  • pnpm test — 6283/6283 ghost/core unit tests pass after updating the four direct-transport name assertions in ghost-mailer.test.js (the pre-existing comments-ui editor markdown flake reproduces on clean main and is unrelated)
  • Closes 4 nodemailer advisories: GHSA-rcmh-qjqh-p98v (high, addressparser DoS), GHSA-mm7p-fcc7-pg87 (moderate, email-to-unintended-domain), GHSA-vvjj-xcjg-gr5g (moderate, SMTP CRLF), GHSA-c7w3-x93f-qmm8 (low, envelope.size SMTP injection)
  • CI runs the full integration suite under canonical conditions; reviewers with AWS SES configurations should verify SESv2 IAM coverage before deploying

- ghost/core was on 0.3.48, two majors behind the latest published version.
  2.2.0 declares nodemailer 8.0.5 directly, replacing the transitively-pulled
  6.10.1 that had four advisories against it
- Public API of @tryghost/nodemailer is unchanged: same module.exports
  function (transport, options) signature, same set of supported transport
  names (smtp, mailgun, sendmail, ses, direct, stub)
- Two internal behavior changes:
  * `direct` now uses nodemailer's built-in {direct: true} option instead of
    the abandoned nodemailer-direct-transport wrapper. Functionally
    equivalent — direct mode is still active — but the transporter's name
    metadata is now 'SMTP' instead of 'SMTP (direct)'. Test assertions
    updated to match and to also verify options.direct === true so the
    direct-mode coverage is preserved
  * `ses` migrates from @aws-sdk/client-ses (v1: SES + SendRawEmail-by-API)
    to @aws-sdk/client-sesv2 (v2: SESv2Client + SendRawEmailCommand). AWS
    SES users may need to verify their IAM permissions cover the v2 API
    surface; this transition was an upstream choice in @tryghost/nodemailer
    2.2.0 itself
- Closes 4 advisories (1 high, 2 mod, 1 low) all rooted in nodemailer 6.10.1
- Verified: pnpm test passes 6283/6283 ghost/core (the comments-ui editor
  markdown flake reproduces on clean main and is unrelated); single
  nodemailer version (8.0.5) and single @tryghost/nodemailer version (2.2.0)
  in the resolved tree
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 5, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 026f17f0-0009-4c42-af70-811867580a1a

📥 Commits

Reviewing files that changed from the base of the PR and between 7ddd8db and bbc2d90.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (2)
  • ghost/core/package.json
  • ghost/core/test/unit/server/services/mail/ghost-mailer.test.js

Walkthrough

The @tryghost/nodemailer dependency in ghost/core/package.json is upgraded from version 0.3.48 to 2.2.0. Corresponding test updates are made in ghost-mailer.test.js to reflect changes in how direct mail delivery is configured. The transport naming scheme changes from 'SMTP (direct)' to 'SMTP' with an explicit direct: true flag in the transporter options. Test assertions are updated to verify both the transport name and the direct flag before sending mail in direct delivery scenarios.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and directly summarizes the main change: bumping the @tryghost/nodemailer dependency version in ghost/core.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, explaining the why, behavioral changes, and test plan for the nodemailer dependency bump.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch security/nodemailer-bump

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 5, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 73.19%. Comparing base (7ddd8db) to head (bbc2d90).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #27686      +/-   ##
==========================================
- Coverage   73.19%   73.19%   -0.01%     
==========================================
  Files        1561     1561              
  Lines      127073   127073              
  Branches    15396    15393       -3     
==========================================
- Hits        93014    93009       -5     
+ Misses      33101    33087      -14     
- Partials      958      977      +19     
Flag Coverage Δ
admin-tests 49.89% <ø> (+0.01%) ⬆️
e2e-tests 73.19% <ø> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@9larsons 9larsons merged commit e35122a into main May 5, 2026
46 checks passed
@9larsons 9larsons deleted the security/nodemailer-bump branch May 5, 2026 21:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant