Skip to content

fix(email): HTML-escape interpolated values in email templates#48

Open
clawedassistant26 wants to merge 1 commit into
moshcoder:masterfrom
clawedassistant26:fix/escape-html-email-templates
Open

fix(email): HTML-escape interpolated values in email templates#48
clawedassistant26 wants to merge 1 commit into
moshcoder:masterfrom
clawedassistant26:fix/escape-html-email-templates

Conversation

@clawedassistant26

Copy link
Copy Markdown

What

Dynamic values were interpolated straight into the HTML email bodies in lib/email.ts with no escaping.

  • sendGithubClosedNotification renders title, repo and actor into HTML. A GitHub issue/PR title is attacker-controlled — anyone can open an issue titled <script>... or <img src=x onerror=...>, which then lands unescaped in the repo owner's notification email.
  • sendWaitlistVerification and sendPasswordReset interpolate the tenant brand into HTML the same way.

Reproduce (before)

Stub fetch, call sendGithubClosedNotification({ title: '<script>alert(1)</script>', actor: 'evil"><b>x</b>', ... }), and the outgoing Resend html body contains the raw <script> / <b> markup.

Fix

Add a small escapeHtml helper and apply it to every dynamic value that lands in an HTML body (brand, title, repo, actor, and the link URL). Plain-text bodies and subjects are intentionally left untouched — they are text/plain, not HTML. Benign brand names render exactly as before.

Test

Adds tests/email-escape.test.mjs which captures the outgoing payload and asserts the HTML body is escaped. It fails on the current code (3/4) and passes after the fix (4/4). Full existing suite stays green (the one unrelated failure, affiliate-commission, is a missing @libsql/client dev-dep in my sandbox, not touched here).

Titles, actors and tenant brand names were interpolated straight into
the HTML email bodies. A GitHub issue/PR title is attacker-controlled,
so sendGithubClosedNotification rendered untrusted markup (e.g.
<script>/<img onerror>) into the owner's notification email; the same
applied to the tenant brand in the waitlist and password-reset emails.

Add an escapeHtml helper and apply it to every dynamic value that lands
in an HTML body. Plain-text bodies and subjects are left untouched.
Adds tests/email-escape.test.mjs (fails before, passes after).
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