fix(core): escape overlapping comment delimiters in escapeCommentText

`COMMENT_DISALLOWED` is matched globally, so overlapping delimiter
sequences are skipped: `<!-->` only escapes the leading `<!--` and
leaves a live `-->` 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.
This commit is contained in:
rootvector2
2026-06-13 22:54:00 +05:30
committed by Jessica Janiuk
parent ea18ab24dd
commit ea1a3ed64c
2 changed files with 29 additions and 4 deletions
+1 -1
View File
@@ -11,7 +11,7 @@
*
* see: https://html.spec.whatwg.org/multipage/syntax.html#comments
*/
const COMMENT_DISALLOWED = /^>|^->|<!--|-->|--!>|<!-$/g;
const COMMENT_DISALLOWED = />|->|<!--|-->|--!>|<!-$/g;
/**
* Delimiter in the disallowed strings which needs to be wrapped with zero with character.
*/
+28 -3
View File
@@ -40,10 +40,35 @@ describe('comment node text escaping', () => {
expect(escapeCommentText('--!>')).toEqual('--!\u200b>\u200b');
expect(escapeCommentText('<!-')).toEqual('\u200b<\u200b!-');
// Things which are OK
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('<!-.')).toEqual('<!-.');
});
it('should escape delimiters that overlap an earlier match', () => {
// `<!-->` contains both `<!--` and a `-->` that shares its `--`; both must be neutralized.
expect(escapeCommentText('<!-->')).toEqual('\u200b<\u200b!--\u200b>\u200b');
expect(escapeCommentText('<!--!>')).toEqual('\u200b<\u200b!--!\u200b>\u200b');
expect(escapeCommentText('a<!-->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 `<!--`), so without escaping
// the trailing markup leaks out of the comment and runs. Round-trip a comment node through
// serialization + re-parsing to confirm the payload stays inert comment text.
const host = document.createElement('div');
host.appendChild(
document.createComment(escapeCommentText('<!--><img src=x onerror="alert(1)">')),
);
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);
});
});
});