From ac462622e9d966113ee6a53ea846c7db992ef3b9 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 9 Apr 2026 12:01:50 +0000
Subject: [PATCH 1/2] Initial plan


From e7701fe3f472de37105869e8073a04c4e0680be3 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 9 Apr 2026 12:23:42 +0000
Subject: [PATCH 2/2] fix: reorder removeXmlComments before mention
 neutralization to prevent bypass

HTML comments containing @mentions could survive sanitization because
neutralizeAllMentions wrapped @mentions in backticks before removeXmlComments
ran. The backtick-wrapped mention split the <!--...--> pattern across
inline-code boundaries, causing applyFnOutsideInlineCode to never pass a
complete comment pattern to removeXmlComments.

Fix: move applyToNonCodeRegions(removeXmlComments) before
neutralizeAllMentions/neutralizeMentions in both sanitize_content_core.cjs
and sanitize_content.cjs.

Adds regression tests for <!-- @mention payload --> patterns.

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/ac4b8c22-07a3-4df6-b7ae-991bf386ec7b

Co-authored-by: szabta89 <1330202+szabta89@users.noreply.github.com>
---
 actions/setup/js/sanitize_content.cjs      |  9 ++++++---
 actions/setup/js/sanitize_content.test.cjs | 17 +++++++++++++++++
 actions/setup/js/sanitize_content_core.cjs |  9 ++++++---
 3 files changed, 29 insertions(+), 6 deletions(-)

diff --git a/actions/setup/js/sanitize_content.cjs b/actions/setup/js/sanitize_content.cjs
index 5ce4b056c46..8218acdcdff 100644
--- a/actions/setup/js/sanitize_content.cjs
+++ b/actions/setup/js/sanitize_content.cjs
@@ -88,12 +88,15 @@ function sanitizeContent(content, maxLengthOrOptions) {
   // Neutralize commands at the start of text
   sanitized = neutralizeCommands(sanitized);
 
+  // Remove XML comments before mention neutralization to prevent bypass: if removeXmlComments
+  // ran after neutralizeMentions, a comment like <!-- @user payload --> would first become
+  // <!-- `@user` payload --> and applyFnOutsideInlineCode would split at the backtick boundary,
+  // preventing the full <!--...--> pattern from being matched.
+  sanitized = applyToNonCodeRegions(sanitized, removeXmlComments);
+
   // Neutralize @mentions with selective filtering (custom logic for allowed aliases)
   sanitized = neutralizeMentions(sanitized, allowedAliasesLowercase);
 
-  // Remove XML comments – skip code blocks and inline code
-  sanitized = applyToNonCodeRegions(sanitized, removeXmlComments);
-
   // Convert XML tags – skip code blocks and inline code
   sanitized = applyToNonCodeRegions(sanitized, convertXmlTags);
 
diff --git a/actions/setup/js/sanitize_content.test.cjs b/actions/setup/js/sanitize_content.test.cjs
index 7ec263c5f8d..10dd7dcb158 100644
--- a/actions/setup/js/sanitize_content.test.cjs
+++ b/actions/setup/js/sanitize_content.test.cjs
@@ -289,6 +289,23 @@ describe("sanitize_content.cjs", () => {
       const result = sanitizeContent("Hello <!-- multi\nline\ncomment --> world");
       expect(result).toBe("Hello  world");
     });
+
+    it("should remove XML comments containing @mentions (regression: bypass via backtick wrapping)", () => {
+      // If removeXmlComments ran after neutralizeMentions, the @mention would be wrapped in
+      // backticks first, splitting the <!--...--> pattern and causing it to survive sanitization.
+      const result = sanitizeContent("<!-- @exploituser injected payload -->");
+      expect(result).toBe("");
+    });
+
+    it("should remove XML comments containing multiple @mentions", () => {
+      const result = sanitizeContent("<!-- @attacker1 and @attacker2 payload -->");
+      expect(result).toBe("");
+    });
+
+    it("should remove XML comments with @mentions mixed with surrounding text", () => {
+      const result = sanitizeContent("before <!-- @exploituser payload --> after");
+      expect(result).toBe("before  after");
+    });
   });
 
   describe("XML/HTML tag conversion", () => {
diff --git a/actions/setup/js/sanitize_content_core.cjs b/actions/setup/js/sanitize_content_core.cjs
index 5913186a4ef..36e444deef9 100644
--- a/actions/setup/js/sanitize_content_core.cjs
+++ b/actions/setup/js/sanitize_content_core.cjs
@@ -1035,12 +1035,15 @@ function sanitizeContentCore(content, maxLength, maxBotMentions) {
   // Neutralize commands at the start of text (e.g., /bot-name)
   sanitized = neutralizeCommands(sanitized);
 
+  // Remove XML comments before mention neutralization to prevent bypass: if removeXmlComments
+  // ran after neutralizeAllMentions, a comment like <!-- @user payload --> would first become
+  // <!-- `@user` payload --> and applyFnOutsideInlineCode would split at the backtick boundary,
+  // preventing the full <!--...--> pattern from being matched.
+  sanitized = applyToNonCodeRegions(sanitized, removeXmlComments);
+
   // Neutralize ALL @mentions (no filtering in core version)
   sanitized = neutralizeAllMentions(sanitized);
 
-  // Remove XML comments – skip code blocks and inline code to avoid altering code content
-  sanitized = applyToNonCodeRegions(sanitized, removeXmlComments);
-
   // Convert XML tags to parentheses format – skip code blocks and inline code so that
   // type parameters (e.g. VBuffer<float32>) and code containing angle brackets are preserved
   sanitized = applyToNonCodeRegions(sanitized, convertXmlTags);
