From d1598062e6414faa51f8f4155eba7ba8a5ec3fbb Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 25 Feb 2026 12:06:29 +0000
Subject: [PATCH 1/2] Initial plan
From aa51d4883fc65c340d8f289b52783ffa0136cfaa Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 25 Feb 2026 12:14:59 +0000
Subject: [PATCH 2/2] fix: only convert known HTML/SVG elements in safe-outputs
sanitizer
Custom XML tags (e.g., MSBuild's AppendTargetFrameworkToOutputPath) are
no longer converted to parentheses. Only known HTML5 and SVG elements
are converted to prevent HTML injection, since GitHub's markdown
renderer cannot interpret custom XML as HTML anyway.
Fixes: safe-outputs add-comment strips < and > from markdown code blocks
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
.github/smoke-test-22395402042.json | 2 +-
actions/setup/js/sanitize_content.test.cjs | 20 +++++
actions/setup/js/sanitize_content_core.cjs | 100 ++++++++++++++++++++-
3 files changed, 119 insertions(+), 3 deletions(-)
diff --git a/.github/smoke-test-22395402042.json b/.github/smoke-test-22395402042.json
index 8f941793eb9..eb8cab0afc6 100644
--- a/.github/smoke-test-22395402042.json
+++ b/.github/smoke-test-22395402042.json
@@ -1 +1 @@
-{"smoke_test": "run-22395402042", "note": "Test file for PR push validation"}
+{ "smoke_test": "run-22395402042", "note": "Test file for PR push validation" }
diff --git a/actions/setup/js/sanitize_content.test.cjs b/actions/setup/js/sanitize_content.test.cjs
index 8c383588624..174b6ecc0b4 100644
--- a/actions/setup/js/sanitize_content.test.cjs
+++ b/actions/setup/js/sanitize_content.test.cjs
@@ -482,6 +482,26 @@ describe("sanitize_content.cjs", () => {
const result = sanitizeContent(input);
expect(result).toBe(input);
});
+
+ it("should preserve custom XML tags that are not HTML elements", () => {
+ const input = "false";
+ const result = sanitizeContent(input);
+ expect(result).toBe(input);
+ });
+
+ it("should preserve MSBuild XML tags in markdown code blocks", () => {
+ const input = "```xml\nfalse\n```";
+ const result = sanitizeContent(input);
+ expect(result).toBe(input);
+ });
+
+ it("should still convert known HTML elements like div and script", () => {
+ const result1 = sanitizeContent("
content
");
+ expect(result1).toBe("(div)content(/div)");
+
+ const result2 = sanitizeContent("");
+ expect(result2).toBe("(script)alert('xss')(/script)");
+ });
});
describe("ANSI escape sequence removal", () => {
diff --git a/actions/setup/js/sanitize_content_core.cjs b/actions/setup/js/sanitize_content_core.cjs
index ea83b2b8071..cd4967cf9b2 100644
--- a/actions/setup/js/sanitize_content_core.cjs
+++ b/actions/setup/js/sanitize_content_core.cjs
@@ -380,6 +380,99 @@ function convertXmlTags(s) {
"ul",
];
+ // Known HTML5 elements - only these will be converted to prevent HTML injection.
+ // Custom XML elements (e.g., MSBuild, SVG custom, proprietary) are left unchanged
+ // since they cannot be rendered as HTML by GitHub's markdown renderer.
+ const knownHtmlElements = new Set([
+ ...allowedTags,
+ "a",
+ "address",
+ "area",
+ "article",
+ "aside",
+ "audio",
+ "base",
+ "bdi",
+ "bdo",
+ "body",
+ "button",
+ "canvas",
+ "caption",
+ "cite",
+ "col",
+ "colgroup",
+ "data",
+ "datalist",
+ "dd",
+ "dfn",
+ "dialog",
+ "div",
+ "dl",
+ "dt",
+ "embed",
+ "fieldset",
+ "figcaption",
+ "figure",
+ "footer",
+ "form",
+ "head",
+ "header",
+ "hgroup",
+ "html",
+ "iframe",
+ "img",
+ "input",
+ "label",
+ "legend",
+ "link",
+ "main",
+ "map",
+ "menu",
+ "meta",
+ "meter",
+ "nav",
+ "noscript",
+ "object",
+ "optgroup",
+ "option",
+ "output",
+ "picture",
+ "progress",
+ "q",
+ "rp",
+ "rt",
+ "ruby",
+ "samp",
+ "script",
+ "section",
+ "select",
+ "slot",
+ "small",
+ "source",
+ "style",
+ "template",
+ "textarea",
+ "tfoot",
+ "time",
+ "title",
+ "track",
+ "u",
+ "var",
+ "video",
+ "wbr",
+ ]);
+
+ // SVG elements that can be embedded in HTML5 and exploited via event handlers.
+ // Each element name is derived from the SVG specification.
+ for (const svgEl of "animate animateMotion animateTransform circle clipPath defs desc ellipse feBlend feColorMatrix feComponentTransfer feComposite feConvolveMatrix feDiffuseLighting feDisplacementMap feDropShadow feFlood feFuncA feFuncB feFuncG feFuncR feGaussianBlur feImage feMerge feMergeNode feMorphology feOffset fePointLight feSpecularLighting feSpotLight feTile feTurbulence filter foreignObject g image line linearGradient marker mask metadata mpath path pattern polygon polyline radialGradient rect set stop svg switch symbol text textPath tspan use view".split(
+ " "
+ )) {
+ knownHtmlElements.add(svgEl);
+ knownHtmlElements.add(svgEl.toLowerCase());
+ }
+ // MathML root element that can be embedded in HTML5.
+ knownHtmlElements.add("math");
+
// First, process CDATA sections specially - convert tags inside them and the CDATA markers
s = s.replace(//g, (match, content) => {
// Convert tags inside CDATA content
@@ -391,7 +484,7 @@ function convertXmlTags(s) {
// Convert opening tags: or to (tag) or (tag attr="value")
// Convert closing tags: to (/tag)
// Convert self-closing tags: or to (tag/) or (tag /)
- // But preserve allowed safe tags
+ // But preserve allowed safe tags and unknown (non-HTML) custom XML tags
return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => {
// Extract tag name from the content (handle closing tags and attributes)
const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/);
@@ -400,8 +493,11 @@ function convertXmlTags(s) {
if (allowedTags.includes(tagName)) {
return match; // Preserve allowed tags
}
+ if (!knownHtmlElements.has(tagName)) {
+ return match; // Preserve non-HTML custom XML tags (e.g., MSBuild, custom namespaces)
+ }
}
- return `(${tagContent})`; // Convert other tags to parentheses
+ return `(${tagContent})`; // Convert known HTML tags to parentheses
});
}