From a022667bd468a066eee078d85160a9390aace838 Mon Sep 17 00:00:00 2001 From: RubenPari Date: Wed, 12 Aug 2026 22:48:47 +0200 Subject: [PATCH] fix: escape fallback raw-content ancestor tags in processing instructions serializeOne()'s PROCESSING_INSTRUCTION_NODE branch never called fallbackRawContentTags()/escapeMatchingClosingTag(), unlike the COMMENT_NODE branch fixed in fc7e40a. A literal "'), so it reaches the browser's RAWTEXT tokenizer unescaped and closes the fallback element early, exposing following sibling markup as live DOM. Mirrors the fix already applied to the comment-node branch. --- lib/NodeUtils.js | 8 +++++++- test/xss.js | 21 +++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/lib/NodeUtils.js b/lib/NodeUtils.js index a0a4962..7900a99 100644 --- a/lib/NodeUtils.js +++ b/lib/NodeUtils.js @@ -309,7 +309,13 @@ function serializeOne(kid, parent) { s += ''; break; case 7: //PROCESSING_INSTRUCTION_NODE - const content = escapeProcessingInstructionContent(kid.data); + let content = escapeProcessingInstructionContent(kid.data); + if (content.includes(''; break; case 10: //DOCUMENT_TYPE_NODE diff --git a/test/xss.js b/test/xss.js index 3988b28..002ec72 100644 --- a/test/xss.js +++ b/test/xss.js @@ -509,6 +509,27 @@ exports.badProcessingInstruction = function () { return alertFired(html).should.eventually.be.false('alert fired for: ' + html); }; +exports.noscriptProcessingInstructionAncestorClosingTagEscaped = function () { + const document = domino.createDocument(''); + const noscript = document.createElement('noscript'); + const pi = document.createProcessingInstruction('x', '', + ); + + const html = document.serialize(); + return alertFired(html).should.eventually.be.false('alert fired for: ' + html); +}; + exports.verifyEscapeMatchingClosingTag = function () { const cases = [ ['', 'style', ''], // no artifacts while processing an empty string