From 408dbe2b64ebc5bbb0e624ffbf15992f597eb9a6 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 2 Jun 2026 05:18:50 +0000 Subject: [PATCH 1/2] fix(plugin-email): harden htmlToText against double-escaping and incomplete tag stripping Resolve two CodeQL high-severity alerts in htmlToText: - js/double-escaping: replace the order-dependent chain of single-entity .replace() calls with one single-pass alternation regex, so sequences like &lt; decode once to the literal < instead of cascading to <. - js/incomplete-multi-character-sanitization: loop tag stripping until the string is stable, so crafted/overlapping input (e.g. ipt>) cannot leave a live tag behind. Tags are now stripped before entities are decoded so decoding cannot re-introduce a live tag. Adds adversarial unit tests covering nested entities and overlapping tags. --- .../plugin-email/src/template-engine.test.ts | 33 ++++++++++ .../plugin-email/src/template-engine.ts | 60 ++++++++++++++++--- 2 files changed, 84 insertions(+), 9 deletions(-) diff --git a/packages/plugins/plugin-email/src/template-engine.test.ts b/packages/plugins/plugin-email/src/template-engine.test.ts index 2475b5386..645fabc36 100644 --- a/packages/plugins/plugin-email/src/template-engine.test.ts +++ b/packages/plugins/plugin-email/src/template-engine.test.ts @@ -72,5 +72,38 @@ describe('template-engine', () => { it('collapses 3+ newlines to 2', () => { expect(htmlToText('

a

b

')).toBe('a\nb'); }); + + describe('adversarial sanitization', () => { + it('does not double-unescape entities', () => { + // &lt; must decode ONCE to the literal text "<", never to "<". + const out = htmlToText('&lt;script&gt;'); + expect(out).toBe('<script>'); + expect(out).not.toContain('<'); + expect(out).not.toContain('>'); + }); + + it('decodes single-escaped entities exactly once', () => { + // Sanity counterpart: single-escaped sequences still decode normally. + expect(htmlToText('a && b')).toBe('a && b'); + }); + + it('strips overlapping/nested tags so no tag survives', () => { + const out = htmlToText('ipt>alert(1)'); + expect(out).not.toContain('<'); + expect(out.toLowerCase()).not.toContain(' { + const out = htmlToText('<