Skip to content

fix: HTML-escape sender/subject in reply/forward quote header (#482)#488

Merged
rathlinus merged 1 commit into
bulwarkmail:mainfrom
hildebrandttk:fix/quote-header-sender-address-482
Jun 24, 2026
Merged

fix: HTML-escape sender/subject in reply/forward quote header (#482)#488
rathlinus merged 1 commit into
bulwarkmail:mainfrom
hildebrandttk:fix/quote-header-sender-address-482

Conversation

@hildebrandttk

Copy link
Copy Markdown
Contributor

Summary

Replying to / forwarding a message whose sender has a display name produced a
quote header that dropped the email address:

Expected: From: Display Name user@domain.tld
Actual: From: Display Name

The forward header builder already emits the full "Name " sender. The bug
is that the HTML variant interpolated that string UNESCAPED:

lib/quote-header.ts (forward):
const html = ... From: ${fromStrFull} ...; // fromStrFull = "Name "

The composer is rich-text (HTML). When this markup is inserted into the editor,
the browser parses the "user@domain.tld" run as a bogus/unknown HTML tag and
does not render it - so only "From: Display Name" remains. The plain-text variant
and the message details panel escape correctly, which is why the address shows
THERE but vanishes in the composer body (the reporter read this as "the data is
present, so it's a builder issue" - correct: it is an HTML-escaping bug).

Reproduced directly against current main:

FORWARD html =

... From: Display Name user@domain.tld
...

^^^^^^^^^^^^^^^^^^^ eaten as a tag

Regression origin: PR #367 ("localize reply/forward quote header incl. sender
address", commit 05e2837) added the "" into the HTML string without
escaping it. Before #367 the forward header showed only the bare name, so the
missing-address symptom is new since #367.

Changes

HTML-escape the user-controlled values (sender, subject, date) in EVERY HTML
quote-header path; the plain-text variants are unchanged. Uses the existing
escapeHtml() from lib/email-sanitization.ts.

  • lib/quote-header.ts (production path, via app/(main)/[locale]/page.tsx ->
    buildQuoteHeader): escape fromStrFull + subject + date in the forward HTML;
    escape the reply-line's interpolated from/date in the reply HTML.
  • components/email/email-composer.tsx (inline fallback, used when no plugin/
    page-prepared header is present): same escaping in both HTML branches
    (htmlBody original and plain-body original), for forward and reply.

After the fix the forward HTML is:

From: Display Name <user@domain.tld>

which the editor renders as the intended "From: Display Name user@domain.tld".

Side benefit (security): the subject and display name were previously injected
RAW into the composer document. A crafted subject or display name (e.g.
"") is now escaped - this closes an HTML-injection vector
into the compose editor.

Related Issues

Closes #482

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Refactor / code quality improvement
  • Chore / dependency update / CI change

Checklist

  • I have read the Contributing Guide
  • My code follows the project's code style and conventions
  • I have run npm run typecheck && npm run lint and there are no errors
  • The build passes (npm run build)
  • I have tested my changes locally
  • I have added or updated documentation if needed
  • I have updated translations (locales/) if my changes affect user-facing text
  • I have included screenshots or a screen recording for UI changes

Screenshots / Demo

Notes for Reviewers

…kmail#482)

The forward quote header renders "From: Name <email>", but the HTML variant
interpolated the sender string unescaped. In the rich-text composer the
"<email>" portion is parsed by the browser as a bogus HTML tag and dropped, so
the address silently disappears - the user sees only "From: Display Name". The
plain-text variant and the details panel escape correctly, which is why the
address shows there. This is the regression from bulwarkmail#367, which added the
"<email>" into the HTML string without escaping it.

Fix: HTML-escape the user-controlled values (sender, subject, date) in every
HTML quote-header path - the production builder in lib/quote-header.ts and the
composer's inline fallback (both htmlBody and plain-body branches), for forward
and reply. The reply line keeps the bare display name by design (bulwarkmail#367), but its
HTML form is now escaped too so a display name containing markup can't break
out. As a side benefit this closes an HTML-injection vector: a crafted subject
or display name was previously injected raw into the composer document.

Adds lib/__tests__/quote-header.test.ts covering: forward text keeps
"Name <email>"; forward HTML escapes the angle brackets (address survives) and
a markup subject/display name; reply stays bare-name and HTML-safe.
@rathlinus rathlinus merged commit d863b1f into bulwarkmail:main Jun 24, 2026
@hildebrandttk hildebrandttk deleted the fix/quote-header-sender-address-482 branch June 25, 2026 18:23
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.

[Bug]: Reply/forward quote header omits sender email address when a display name is present

2 participants