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.
This commit is contained in:
simoleo89
2026-06-17 19:00:42 +02:00
parent 1b032bcd23
commit 301294ecf4
7 changed files with 218 additions and 18 deletions
@@ -1,6 +1,6 @@
import { GetSessionDataManager } from '@nitrots/nitro-renderer';
import { FC, useMemo } from 'react';
import { AvatarInfoName } from '../../../../../api';
import { AvatarInfoName, SanitizeHtml } from '../../../../../api';
import { ContextMenuView } from '../../context-menu/ContextMenuView';
interface AvatarInfoWidgetNameViewProps
@@ -24,7 +24,7 @@ export const AvatarInfoWidgetNameView: FC<AvatarInfoWidgetNameViewProps> = props
return (
<ContextMenuView category={ nameInfo.category } classNames={ getClassNames } fades={ (nameInfo.id !== GetSessionDataManager().userId) } objectId={ nameInfo.roomIndex } userType={ nameInfo.userType } onClose={ onClose }>
<div className="text-shadow" dangerouslySetInnerHTML={ { __html: `${ nameInfo.name }` } }></div>
<div className="text-shadow" dangerouslySetInnerHTML={ { __html: SanitizeHtml(`${ nameInfo.name }`) } }></div>
</ContextMenuView>
);
};