Skip to content

🐛 Fixed HTML entities appearing in email publication dates#27655

Open
schalkneethling wants to merge 2 commits intoTryGhost:mainfrom
schalkneethling:26905-html-entities-email
Open

🐛 Fixed HTML entities appearing in email publication dates#27655
schalkneethling wants to merge 2 commits intoTryGhost:mainfrom
schalkneethling:26905-html-entities-email

Conversation

@schalkneethling
Copy link
Copy Markdown

@schalkneethling schalkneethling commented May 3, 2026

ref #26905

  • i18next's default escapeValue:true was encoding forward slashes as / hex entities in interpolated values, causing dates like "19/03/2026" to appear as "19/03/2026" in plaintext email parts

  • changed i18n interpolation to escapeValue:false for all namespaces (was only set for theme), since Handlebars already handles HTML escaping in email templates and React/JSX handles it in client apps

  • added defense-in-depth / to / cleanup in the email rendering pipeline to catch any hex-encoded slashes from cheerio/juice serialization

  • reordered plaintext generation to run after entity fixes so the text/plain MIME part also receives clean, decoded text

  • I've read and followed the Contributor Guide

  • I've explained my change

  • I've written an automated test to prove my change works


Note

Low Risk
Low risk string-normalization change in the email rendering pipeline plus a unit test; primary risk is unintended replacement of legitimate numeric entities, but it’s narrowly scoped to forward-slash encodings.

Overview
Prevents forward slashes from being serialized as hex/numeric HTML entities (e.g. /) during email HTML processing, which could show up literally in inbox previews and text/plain parts.

Adds a defense-in-depth cleanup in both email-rendering/finalize.js and EmailRenderer.renderBody, and reorders plaintext generation to run after entity fixes so the plaintext output is also clean. Includes a unit test asserting both HTML and plaintext contain raw / for interpolated values (issue #26905).

Reviewed by Cursor Bugbot for commit 51828c6. Bugbot is set up for automated code reviews on this repo. Configure here.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 3, 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: 573ec39b-ef82-4ceb-bd10-db80d9e4842a

📥 Commits

Reviewing files that changed from the base of the PR and between 51828c6 and b952171.

📒 Files selected for processing (3)
  • ghost/core/core/server/services/email-rendering/finalize.js
  • ghost/core/core/server/services/email-service/email-renderer.js
  • ghost/core/test/unit/server/services/email-service/email-renderer.test.js
✅ Files skipped from review due to trivial changes (3)
  • ghost/core/core/server/services/email-rendering/finalize.js
  • ghost/core/core/server/services/email-service/email-renderer.js
  • ghost/core/test/unit/server/services/email-service/email-renderer.test.js

Walkthrough

This pull request adds normalization to prevent forward-slash hex entities from appearing in rendered email content. finalize.js now replaces hex-encoded forward slashes (e.g. /, /) with literal /. email-renderer.js is adjusted to apply Outlook/serialization character fixes and forward-slash normalization to the HTML before converting it to plaintext. A new unit test verifies that author names and formatted dates containing / appear as raw slashes in both HTML and plaintext and that slash entities are absent.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title mentions fixing HTML entities in email publication dates, which aligns with the core issue (forward slashes encoded as /), but downplays the broader scope of entity normalization across the email rendering pipeline and i18n escaping changes.
Description check ✅ Passed The description comprehensively explains the root cause (i18next escapeValue:true), the primary fix (escapeValue:false for all namespaces), the defense-in-depth additions, and references the issue and test coverage.
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 unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ast-grep (0.42.1)
ghost/core/test/unit/server/services/email-service/email-renderer.test.js

[]


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
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ghost/core/test/unit/server/services/email-service/email-renderer.test.js`:
- Around line 2413-2423: The test currently only checks for 'Author/Name' and a
formatted date but doesn't ensure slashes in dates aren't HTML-encoded; update
the assertions in the email-renderer test that calls renderBody() to also verify
that slash-encoding is not present by asserting that response.html and
response.plaintext do NOT contain any slash HTML-entities (e.g. '/' or
'/' / case variants), or alternatively adjust the test input to pass a
publishedAt string that contains a literal '/' so renderBody(publishedAt) is
exercised for slash characters; reference response.html, response.plaintext and
the renderBody() invocation when making the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6571cb98-8f89-4e48-b3b2-7d171af69380

📥 Commits

Reviewing files that changed from the base of the PR and between b08cf75 and 76442b3.

📒 Files selected for processing (4)
  • ghost/core/core/server/services/email-rendering/finalize.js
  • ghost/core/core/server/services/email-service/email-renderer.js
  • ghost/core/test/unit/server/services/email-service/email-renderer.test.js
  • ghost/i18n/lib/i18n.js

Comment thread ghost/core/test/unit/server/services/email-service/email-renderer.test.js Outdated
Comment thread ghost/i18n/lib/i18n.js Outdated
@schalkneethling schalkneethling force-pushed the 26905-html-entities-email branch from 76442b3 to 51828c6 Compare May 3, 2026 21:43
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 51828c6. Configure here.

Comment thread ghost/core/core/server/services/email-service/email-renderer.js Outdated
ref TryGhost#26905

- i18next's escapeValue:true encodes forward slashes as / hex entities in
  interpolated values. When dates or author names containing "/" flow through
  t() helpers into the email template, the resulting / sequences appear as
  literal text in plaintext email parts and inbox previews
- added / to / hex entity decoding in the email rendering pipeline
  (renderBody and finalizeHtml) to clean up any slash entities before the email
  is sent, regardless of where they originate
- reordered plaintext generation to run after entity fixes so the text/plain
  MIME part also receives clean, decoded text
- kept i18next's escapeValue:true intact to preserve XSS protection for
  triple-brace {{{t ...}}} expressions that interpolate user-controlled values
  like site titles and author names
ref TryGhost#26905

- the hex-entity regex only matched / (hex) but not &TryGhost#47; (decimal)
- added |47 alternative to handle the decimal form in case cheerio, juice,
  or any future serializer emits the decimal variant
@schalkneethling schalkneethling force-pushed the 26905-html-entities-email branch from 51828c6 to b952171 Compare May 3, 2026 22:04
@cursor
Copy link
Copy Markdown

cursor Bot commented May 3, 2026

You have used all of your free Bugbot PR reviews.

To receive reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

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