From ea1a3ed64c538fa768fff586796ece6ffb8b6598 Mon Sep 17 00:00:00 2001 From: rootvector2 Date: Sat, 13 Jun 2026 22:54:00 +0530 Subject: [PATCH] fix(core): escape overlapping comment delimiters in escapeCommentText `COMMENT_DISALLOWED` is matched globally, so overlapping delimiter sequences are skipped: `` only escapes the leading `` that can close a programmatically created comment node early. Drop the `^` anchors so a standalone `>`/`->` is escaped wherever it appears, which neutralizes the trailing delimiter left behind by an earlier match. --- packages/core/src/util/dom.ts | 2 +- packages/core/test/util/dom_spec.ts | 31 ++++++++++++++++++++++++++--- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/packages/core/src/util/dom.ts b/packages/core/src/util/dom.ts index a7ab6e3add4..6b43576797d 100644 --- a/packages/core/src/util/dom.ts +++ b/packages/core/src/util/dom.ts @@ -11,7 +11,7 @@ * * see: https://html.spec.whatwg.org/multipage/syntax.html#comments */ -const COMMENT_DISALLOWED = /^>|^->||--!>||->||--!>| { expect(escapeCommentText('--!>')).toEqual('--!\u200b>\u200b'); expect(escapeCommentText('')).toEqual('.>'); - expect(escapeCommentText('.->')).toEqual('.->'); + // A standalone `>` or `->` is escaped regardless of position so that a delimiter which + // overlaps an earlier match (e.g. the `-->` in ``) can't survive a single pass. + expect(escapeCommentText('.>')).toEqual('.\u200b>\u200b'); + expect(escapeCommentText('.->')).toEqual('.-\u200b>\u200b'); expect(escapeCommentText(' { + // `` contains both `` that shares its `--`; both must be neutralized. + expect(escapeCommentText('')).toEqual('\u200b<\u200b!--\u200b>\u200b'); + expect(escapeCommentText('b')).toEqual('a\u200b<\u200b!--\u200b>\u200bb'); + }); + + it('should keep an injected payload inside a programmatically created comment', () => { + // `` closes a comment immediately (the `-->` overlaps the `')), + ); + + const reparsed = document.createElement('div'); + reparsed.innerHTML = host.innerHTML; + + expect(reparsed.childNodes.length).toBe(1); + expect(reparsed.firstChild!.nodeType).toBe(Node.COMMENT_NODE); + expect(reparsed.getElementsByTagName('img').length).toBe(0); + }); }); });