diff --git a/actions/setup/js/resolve_mentions.cjs b/actions/setup/js/resolve_mentions.cjs index aabf44702b..3b201d8bdf 100644 --- a/actions/setup/js/resolve_mentions.cjs +++ b/actions/setup/js/resolve_mentions.cjs @@ -23,7 +23,7 @@ function extractMentions(text) { const { getErrorMessage } = require("./error_helpers.cjs"); - const mentionRegex = /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g; + const mentionRegex = /(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9_-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g; const mentions = []; const seen = new Set(); diff --git a/actions/setup/js/resolve_mentions.test.cjs b/actions/setup/js/resolve_mentions.test.cjs index dbac592f60..a4fecff393 100644 --- a/actions/setup/js/resolve_mentions.test.cjs +++ b/actions/setup/js/resolve_mentions.test.cjs @@ -35,6 +35,29 @@ describe("resolve_mentions.cjs", () => { it("should preserve original case", () => { const mentions = extractMentions("Hello @UserName"); expect(mentions).toEqual(["UserName"]); + }), + it("should extract mentions with underscores", () => { + const mentions = extractMentions("Hello @user_name"); + expect(mentions).toEqual(["user_name"]); + }), + it("should extract mentions with multiple underscores", () => { + const mentions = extractMentions("Hello @user_name_test"); + expect(mentions).toEqual(["user_name_test"]); + }), + it("should extract mentions with underscores and hyphens", () => { + const mentions = extractMentions("Hello @user-name_test"); + expect(mentions).toEqual(["user-name_test"]); + }), + it("should extract mentions with underscores at various positions", () => { + const mentions = extractMentions("@test_user @_invalid @user_ @valid_user_123"); + // @_invalid: starts with underscore - not extracted (starts with non-alphanumeric) + // @user_: ends with underscore - extracted as "user" (underscore not at end) + // Other mentions are valid + expect(mentions).toEqual(["test_user", "user", "valid_user_123"]); + }), + it("should handle org/team mentions with underscores", () => { + const mentions = extractMentions("Hello @my_org/my_team"); + expect(mentions).toEqual(["my_org/my_team"]); })); }), describe("isPayloadUserBot", () => { diff --git a/actions/setup/js/sanitize_content.cjs b/actions/setup/js/sanitize_content.cjs index a68bb2744a..8d1672b1c2 100644 --- a/actions/setup/js/sanitize_content.cjs +++ b/actions/setup/js/sanitize_content.cjs @@ -118,7 +118,7 @@ function sanitizeContent(content, maxLengthOrOptions) { * @returns {string} Processed string */ function neutralizeMentions(s, allowedLowercase) { - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9_-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (_m, p1, p2) => { // Check if this mention is in the allowed aliases list (case-insensitive) const isAllowed = allowedLowercase.includes(p2.toLowerCase()); if (isAllowed) { diff --git a/actions/setup/js/sanitize_content.test.cjs b/actions/setup/js/sanitize_content.test.cjs index b246a25689..10059bec96 100644 --- a/actions/setup/js/sanitize_content.test.cjs +++ b/actions/setup/js/sanitize_content.test.cjs @@ -109,6 +109,26 @@ describe("sanitize_content.cjs", () => { const result = sanitizeContent("Contact email@example.com"); expect(result).toBe("Contact email@example.com"); }); + + it("should neutralize @mentions with underscores", () => { + const result = sanitizeContent("Hello @user_name"); + expect(result).toBe("Hello `@user_name`"); + }); + + it("should neutralize @mentions with multiple underscores", () => { + const result = sanitizeContent("Hello @user_name_test"); + expect(result).toBe("Hello `@user_name_test`"); + }); + + it("should neutralize @mentions with underscores and hyphens", () => { + const result = sanitizeContent("Hello @user-name_test"); + expect(result).toBe("Hello `@user-name_test`"); + }); + + it("should neutralize org/team mentions with underscores", () => { + const result = sanitizeContent("Hello @my_org/my_team"); + expect(result).toBe("Hello `@my_org/my_team`"); + }); }); describe("@mention allowedAliases", () => { @@ -162,6 +182,21 @@ describe("sanitize_content.cjs", () => { expect(result).toBe("Hello `@user`"); }); + it("should not neutralize allowed mentions with underscores", () => { + const result = sanitizeContent("Hello @user_name", { allowedAliases: ["user_name"] }); + expect(result).toBe("Hello @user_name"); + }); + + it("should neutralize disallowed mentions with underscores", () => { + const result = sanitizeContent("Hello @user_name and @other_user", { allowedAliases: ["user_name"] }); + expect(result).toBe("Hello @user_name and `@other_user`"); + }); + + it("should not neutralize org/team mentions with underscores in allowedAliases", () => { + const result = sanitizeContent("Hello @my_org/my_team", { allowedAliases: ["my_org/my_team"] }); + expect(result).toBe("Hello @my_org/my_team"); + }); + it("should log escaped mentions for debugging", () => { const result = sanitizeContent("Hello @user1 and @user2", { allowedAliases: ["user1"] }); expect(result).toBe("Hello @user1 and `@user2`"); diff --git a/actions/setup/js/sanitize_content_core.cjs b/actions/setup/js/sanitize_content_core.cjs index b01198a64a..d4d74e71e0 100644 --- a/actions/setup/js/sanitize_content_core.cjs +++ b/actions/setup/js/sanitize_content_core.cjs @@ -297,7 +297,7 @@ function neutralizeCommands(s) { function neutralizeAllMentions(s) { // Replace @name or @org/team outside code with `@name` // No filtering - all mentions are neutralized - return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { + return s.replace(/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9_-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g, (m, p1, p2) => { // Log when a mention is escaped to help debug issues if (typeof core !== "undefined" && core.info) { core.info(`Escaped mention: @${p2} (not in allowed list)`);