Skip to content

[2.x] fix: Restore held-back email values on every message, and protect core mail - #4988

Merged
imorland merged 1 commit into
2.xfrom
im/mail-safesubstitution-restore
Aug 26, 2026
Merged

imorland merged 1 commit into
2.xfrom
im/mail-safesubstitution-restore

Conversation

@imorland

Copy link
Copy Markdown
Member

Changes proposed in this pull request:

The mail translator replaces parameter values (a discussion title, a display name) with opaque markers before an email is rendered, so they cannot be parsed as markup, and puts them back once rendering is done. That restore only ran inside MailFormatter::convert() — so any template that prints a translated value directly, without the formatter, sent the raw marker to the reader. flarum/gdpr's erasure emails do exactly that, and reported it: the account name and confirmation link arrived as flarumsafevalue…endflarumsafevalue.

This moves the restore onto the finished message, in MutateEmail:

  • Values are put back on the rendered MessageSending body, so it happens for every email regardless of which template or extension produced it — and no template has to change, including ones in extensions that will never be updated.
  • HTML parts are escaped as before (the value is shown, not interpreted). Plain-text parts are restored verbatim — there is no markup to guard against there, and escaping would show entities like & to the reader.
  • A body carrying no markers is left untouched, so mail that never went through the mail translator is unaffected.

It also fixes a gap in the original change: the view composer keyed on the view name containing email, which core's own mail:: views don't — so core notifications were never marked, and the markup-injection protection didn't apply to them at all. Both mail:: views and the extension email/emails convention are now covered.

Reviewers should focus on:

  • That the restore on the message body is correct for both parts — HTML escaped, plain text verbatim — and is a no-op when there are no markers.
  • That marking core mail:: views doesn't double-process values that already go through the formatter (it doesn't — the markers are gone by then, so the message-level restore has nothing to do).

Necessity

  • Has the problem that is being solved here been clearly explained? — extension and core emails leak raw substitution markers; core mail had no injection protection at all.
  • If applicable, have various options for solving this problem been considered? — restoring per-template would need every template changed; the message-level restore fixes it once for all.
  • For core PRs, does this need to be in core, or could it be in an extension? — the mail translator/formatter and the send pipeline are core.
  • Are we willing to maintain this for years / potentially forever?

Confirmed

  • Frontend changes: tested on a local Flarum installation. — no frontend changes.
  • Frontend changes: tests are green — n/a.
  • Frontend changes: tests have been added — n/a.
  • Backend changes: tests are green (run composer test).
  • Backend changes: tests have been added, or are not appropriate here. — new tests send real mail and assert the finished message carries no markers, escapes HTML, leaves plain text verbatim, and that a core mail:: view is protected.
  • Where applicable, changes are suitable for all supported database drivers (MySQL, MariaDB, PostgreSQL, SQLite). — no database involvement.
  • The description above is written by me and describes what this pull request actually does.

Required changes:

  • Related documentation PR: (Remove if irrelevant)

The mail translator replaces parameter values with markers before an
email is rendered, so a discussion title or display name cannot be
parsed as markup, and puts them back afterwards. Restoring only happened
inside MailFormatter::convert(), so any template that printed a
translated value without the formatter - flarum/gdpr's erasure emails,
for one - sent the raw marker to the reader.

Restore the values on the finished message instead, in MutateEmail, so
it happens for every email whatever template or extension produced it,
and no template has to change. HTML parts are escaped as before; plain
text parts are put back verbatim, since there is no markup to guard
against there and escaping would show entities to the reader.

Also mark core's own 'mail::' views, which the previous name check
missed - so core notifications get the same protection, not only
extension mail.
@imorland
imorland requested a review from a team as a code owner August 26, 2026 10:08
@imorland imorland added this to the 2.0.0-rc.8 milestone Aug 26, 2026
@imorland imorland changed the title Restore held-back email values on every message, and protect core mail [2.x] fix: Restore held-back email values on every message, and protect core mail Aug 26, 2026
@imorland
imorland merged commit d975c02 into 2.x Aug 26, 2026
30 checks passed
@imorland
imorland deleted the im/mail-safesubstitution-restore branch August 26, 2026 14:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant