From 80bd0003ce2bb03f2c8df32e8fcf62f4b8bdaf21 Mon Sep 17 00:00:00 2001 From: ly-wang19 <94427531+ly-wang19@users.noreply.github.com> Date: Wed, 24 Jun 2026 21:34:40 +0800 Subject: [PATCH] fix(msteams): decode & last in stripHtmlFromTeamsMessage to avoid double-decoding (#96342) stripHtmlFromTeamsMessage decoded & FIRST, so literal entity text the user typed (which Microsoft Graph returns double-encoded, e.g. &lt;) got re-decoded into markup: "The token is &lt;APIKEY&gt;" became "The token is " instead of the correct "The token is <APIKEY>". Reorder so & is decoded last, mirroring the documented ordering in decodeHtmlEntities (inbound.ts), whose comment already states it 'must be last to prevent double-decoding (e.g. &lt; -> < not <)'. Behavior-preserving for all singly-encoded input; the existing entity test is unchanged. Co-authored-by: ly-wang19 Co-authored-by: Claude Opus 4.8 (1M context) --- extensions/msteams/src/graph-thread.test.ts | 9 +++++++++ extensions/msteams/src/graph-thread.ts | 8 +++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/extensions/msteams/src/graph-thread.test.ts b/extensions/msteams/src/graph-thread.test.ts index b34b76ccc95..77164b1c469 100644 --- a/extensions/msteams/src/graph-thread.test.ts +++ b/extensions/msteams/src/graph-thread.test.ts @@ -37,6 +37,15 @@ describe("stripHtmlFromTeamsMessage", () => { ); }); + it("does not double-decode escaped entities (decodes & last)", () => { + // Graph encodes literally-typed entity text by escaping its '&' to '&'. + // Decoding '&' first would re-decode the now-bare '<'/'>' into + // angle brackets, corrupting the user's literal text. + expect(stripHtmlFromTeamsMessage("The token is &lt;APIKEY&gt;")).toBe( + "The token is <APIKEY>", + ); + }); + it("normalizes multiple whitespace to single space", () => { expect(stripHtmlFromTeamsMessage("hello world")).toBe("hello world"); }); diff --git a/extensions/msteams/src/graph-thread.ts b/extensions/msteams/src/graph-thread.ts index 88b37311991..4254af7698f 100644 --- a/extensions/msteams/src/graph-thread.ts +++ b/extensions/msteams/src/graph-thread.ts @@ -35,14 +35,16 @@ export function stripHtmlFromTeamsMessage(html: string): string { let text = html.replace(/]*>(.*?)<\/at>/gi, "@$1"); // Strip remaining HTML tags. text = text.replace(/<[^>]*>/g, " "); - // Decode common HTML entities. + // Decode common HTML entities. & must be decoded LAST to prevent + // double-decoding (e.g. &lt; → < not <), matching decodeHtmlEntities + // in inbound.ts. text = text - .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, '"') .replace(/'/g, "'") - .replace(/ /g, " "); + .replace(/ /g, " ") + .replace(/&/g, "&"); // Normalize whitespace. return text.replace(/\s+/g, " ").trim(); }