From 301294ecf4a395fafdb60d5b729b9252699e9e45 Mon Sep 17 00:00:00 2001
From: simoleo89 <11816867+simoleo89@users.noreply.github.com>
Date: Wed, 17 Jun 2026 19:00:42 +0200
Subject: [PATCH] fix(security): sanitize user-controlled HTML in chat &
username sinks
Several dangerouslySetInnerHTML sinks rendered user-controlled strings (chat messages, usernames, chat history) without sanitisation, relying implicitly on upstream formatting or server-side charset limits. Route them all through the existing SanitizeHtml (DOMPurify) helper so the security guarantee is local to each render site.
Sinks fixed: ChatWidgetWindowView (name/message/original/translated), ChatHistoryView (name/message), AvatarInfoWidgetNameView + AvatarInfoWidgetAvatarView (username), SelectReportedUserView (username).
Add regression suites: SanitizeHtml.test.ts (XSS neutralised, chat markup preserved) and RoomChatFormatter.test.ts (pins the existing encodeHTML defence). No behaviour change: SanitizeHtml's allow-list keeps the b/i/u/span/strong/em/br markup the chat/profile UI relies on.
---
src/api/utils/RoomChatFormatter.test.ts | 112 ++++++++++++++++++
src/api/utils/SanitizeHtml.test.ts | 88 ++++++++++++++
.../chat-history/ChatHistoryView.tsx | 6 +-
.../help/views/SelectReportedUserView.tsx | 4 +-
.../menu/AvatarInfoWidgetAvatarView.tsx | 4 +-
.../menu/AvatarInfoWidgetNameView.tsx | 4 +-
.../widgets/chat/ChatWidgetWindowView.tsx | 18 +--
7 files changed, 218 insertions(+), 18 deletions(-)
create mode 100644 src/api/utils/RoomChatFormatter.test.ts
create mode 100644 src/api/utils/SanitizeHtml.test.ts
diff --git a/src/api/utils/RoomChatFormatter.test.ts b/src/api/utils/RoomChatFormatter.test.ts
new file mode 100644
index 0000000..403fd87
--- /dev/null
+++ b/src/api/utils/RoomChatFormatter.test.ts
@@ -0,0 +1,112 @@
+import { describe, expect, it } from 'vitest';
+
+import { RoomChatFormatter } from './RoomChatFormatter';
+
+/**
+ * Security + behaviour suite for the chat formatter.
+ *
+ * The formatter output is injected into the DOM via `dangerouslySetInnerHTML`
+ * in ChatWidgetMessageView, so the security contract is: after the browser
+ * parses the formatted string as HTML, NO attacker-controlled executable
+ * markup may survive (no ');
+ expect(div.querySelector('script')).toBeNull();
+ });
+
+ it('does not produce an element with an onerror handler', () =>
+ {
+ const div = parse('');
+ const img = div.querySelector('img');
+ expect(img).toBeNull();
+ });
+
+ it('does not produce an