matrix-react-sdk vulnerable to HTML injection in search results via plaintext message highlighting
Description
matrix-react-sdk is a react-based SDK for inserting a Matrix chat/VoIP client into a web page. Prior to version 3.71.0, plain text messages containing HTML tags are rendered as HTML in the search results. To exploit this, an attacker needs to trick a user into searching for a specific message containing an HTML injection payload. No cross-site scripting attack is possible due to the hardcoded content security policy. Version 3.71.0 of the SDK patches over the issue. As a workaround, restarting the client will clear the HTML injection.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Matrix React SDK before 3.71.0 renders HTML tags in plain text messages during search, enabling HTML injection via crafted message.
Vulnerability
Description
CVE-2023-30609 is an HTML injection vulnerability in matrix-react-sdk, a React-based SDK for building Matrix chat clients. Prior to version 3.71.0, the search functionality incorrectly treated plain text messages containing HTML tags as raw HTML when displaying results. The root cause is the lack of escaping in the highlighter component; the fix in commit bf182bc introduces escapeHtml to sanitize input before rendering [3].
Exploitation
An attacker can exploit this by tricking a user into searching for a specific message that contains an HTML injection payload. The user must perform the search action, making this a user-initiated attack. Due to a hardcoded Content Security Policy (CSP), cross-site scripting (XSS) is not possible, but HTML injection can still occur, allowing an attacker to inject arbitrary HTML elements into the search results page [1].
Impact
The impact is limited to content spoofing and phishing-style attacks within the search results. An attacker could display fake login prompts, misleading messages, or other UI manipulations. No remote code execution is achievable due to CSP restrictions, but the injected HTML can still deceive users or damage the application's appearance.
Mitigation
The issue is patched in matrix-react-sdk version 3.71.0 [4]. Users should upgrade to this version or later. As a workaround, restarting the client clears the injected HTML from the search results. No other temporary mitigations are documented.
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
matrix-react-sdknpm | < 3.71.0 | 3.71.0 |
Affected products
2- matrix-org/matrix-react-sdkv5Range: < 3.71.0
Patches
1bf182bc94556Merge pull request from GHSA-xv83-x443-7rmw
3 files changed · +55 −9
src/components/views/rooms/SearchResultTile.tsx+1 −1 modified@@ -71,7 +71,7 @@ export default class SearchResultTile extends React.Component<IProps> { for (let j = 0; j < timeline.length; j++) { const mxEv = timeline[j]; - let highlights; + let highlights: string[] | undefined; const contextual = !this.props.ourEventsIndexes.includes(j); if (!contextual) { highlights = this.props.searchHighlights;
src/HtmlUtils.tsx+6 −5 modified@@ -28,6 +28,7 @@ import { decode } from "html-entities"; import { IContent } from "matrix-js-sdk/src/models/event"; import { Optional } from "matrix-events-sdk"; import _Linkify from "linkify-react"; +import escapeHtml from "escape-html"; import { _linkifyElement, @@ -355,10 +356,10 @@ abstract class BaseHighlighter<T extends React.ReactNode> { public constructor(public highlightClass: string, public highlightLink?: string) {} /** - * apply the highlights to a section of text + * Apply the highlights to a section of text * * @param {string} safeSnippet The snippet of text to apply the highlights - * to. + * to. This input must be sanitised as it will be treated as HTML. * @param {string[]} safeHighlights A list of substrings to highlight, * sorted by descending length. * @@ -367,7 +368,7 @@ abstract class BaseHighlighter<T extends React.ReactNode> { */ public applyHighlights(safeSnippet: string, safeHighlights: string[]): T[] { let lastOffset = 0; - let offset; + let offset: number; let nodes: T[] = []; const safeHighlight = safeHighlights[0]; @@ -440,7 +441,7 @@ interface IOpts { } export interface IOptsReturnNode extends IOpts { - returnString: false | undefined; + returnString?: false | undefined; } export interface IOptsReturnString extends IOpts { @@ -574,7 +575,7 @@ export function bodyToHtml(content: IContent, highlights: Optional<string[]>, op safeBody = formatEmojis(safeBody, true).join(""); } } else if (highlighter) { - safeBody = highlighter.applyHighlights(plainBody, safeHighlights!).join(""); + safeBody = highlighter.applyHighlights(escapeHtml(plainBody), safeHighlights!).join(""); } } finally { delete sanitizeParams.textFilter;
test/HtmlUtils-test.tsx+48 −3 modified@@ -14,11 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from "react"; +import React, { ReactElement } from "react"; import { mocked } from "jest-mock"; import { render, screen } from "@testing-library/react"; +import { IContent } from "matrix-js-sdk/src/models/event"; -import { topicToHtml } from "../src/HtmlUtils"; +import { bodyToHtml, topicToHtml } from "../src/HtmlUtils"; import SettingsStore from "../src/settings/SettingsStore"; jest.mock("../src/settings/SettingsStore"); @@ -29,7 +30,7 @@ const enableHtmlTopicFeature = () => { }); }; -describe("HtmlUtils", () => { +describe("topicToHtml", () => { function getContent() { return screen.getByRole("contentinfo").children[0].innerHTML; } @@ -62,3 +63,47 @@ describe("HtmlUtils", () => { expect(getContent()).toEqual('<b>pizza</b> <span class="mx_Emoji" title=":pizza:">🍕</span>'); }); }); + +describe("bodyToHtml", () => { + function getHtml(content: IContent, highlights?: string[]): string { + return (bodyToHtml(content, highlights, {}) as ReactElement).props.dangerouslySetInnerHTML.__html; + } + + it("should apply highlights to HTML messages", () => { + const html = getHtml( + { + body: "test **foo** bar", + msgtype: "m.text", + formatted_body: "test <b>foo</b> bar", + format: "org.matrix.custom.html", + }, + ["test"], + ); + + expect(html).toMatchInlineSnapshot(`"<span class="mx_EventTile_searchHighlight">test</span> <b>foo</b> bar"`); + }); + + it("should apply highlights to plaintext messages", () => { + const html = getHtml( + { + body: "test foo bar", + msgtype: "m.text", + }, + ["test"], + ); + + expect(html).toMatchInlineSnapshot(`"<span class="mx_EventTile_searchHighlight">test</span> foo bar"`); + }); + + it("should not respect HTML tags in plaintext message highlighting", () => { + const html = getHtml( + { + body: "test foo <b>bar", + msgtype: "m.text", + }, + ["test"], + ); + + expect(html).toMatchInlineSnapshot(`"<span class="mx_EventTile_searchHighlight">test</span> foo <b>bar"`); + }); +});
Vulnerability mechanics
Synthesis attempt was rejected by the grounding validator. Re-run pending.
References
5- github.com/advisories/GHSA-xv83-x443-7rmwghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2023-30609ghsaADVISORY
- github.com/matrix-org/matrix-react-sdk/commit/bf182bc94556849d7acdfa0e5fdea2aa129ea826ghsax_refsource_MISCWEB
- github.com/matrix-org/matrix-react-sdk/releases/tag/v3.71.0ghsax_refsource_MISCWEB
- github.com/matrix-org/matrix-react-sdk/security/advisories/GHSA-xv83-x443-7rmwghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.