diff --git a/lib/NodeUtils.js b/lib/NodeUtils.js index 53e5927..eca9b79 100644 --- a/lib/NodeUtils.js +++ b/lib/NodeUtils.js @@ -191,7 +191,9 @@ function serializeOne(kid, parent) { var ss = kid.serialize(); // If an element can have raw content, this content may // potentially require escaping to avoid XSS. - if (hasRawContent[tagname.toUpperCase()]) { + var upperTag = tagname.toUpperCase(); + if (hasRawContent[upperTag] || + (upperTag === 'NOSCRIPT' && kid.ownerDocument._scripting_enabled)) { ss = escapeMatchingClosingTag(ss, tagname); } if (html && extraNewLine[tagname] && ss.charAt(0)==='\n') s += '\n'; diff --git a/test/xss.js b/test/xss.js index 1e60da5..aac140f 100644 --- a/test/xss.js +++ b/test/xss.js @@ -179,6 +179,26 @@ exports.styleMatchingClosingTagSkipsUnclosedCommentedContent = function () { return alertFired(html).should.eventually.be.false('alert fired for: ' + html); }; +exports.noscriptMatchingClosingTagInRawText = function () { + // Use a parsed document so `_scripting_enabled` is true, matching how + // Angular SSR / platform-server uses domino. With scripting enabled, + // '; + document.body.appendChild(noscript); + + document.body + .serialize() + .should.equal(''); + + const html = document.serialize(); + return alertFired(html).should.eventually.be.false('alert fired for: ' + html); +}; + exports.scriptMatchingClosingTagInRawText = function () { const document = domino.createDocument(''); const script = document.createElement('script');