diff --git a/.changeset/sanitize-image-element-url.md b/.changeset/sanitize-image-element-url.md new file mode 100644 index 0000000000000..c2a3a18af8027 --- /dev/null +++ b/.changeset/sanitize-image-element-url.md @@ -0,0 +1,6 @@ +--- +'@rocket.chat/gazzodown': patch +'@rocket.chat/meteor': patch +--- + +Sanitizes image URLs in rendered messages to block `javascript:`, `data:`, and `vbscript:` schemes — matching the protection already applied to markdown links. Defense-in-depth against XSS via crafted markdown like `![label](javascript:...)`. diff --git a/packages/gazzodown/src/Markup.spec.tsx b/packages/gazzodown/src/Markup.spec.tsx index d4f970d031895..c377e94ec2ed0 100644 --- a/packages/gazzodown/src/Markup.spec.tsx +++ b/packages/gazzodown/src/Markup.spec.tsx @@ -345,3 +345,56 @@ it('renders plain text instead of ASCII emojis based on useEmojis preference', ( expect(screen.getByText('Hey! :smile: :)')).toBeInTheDocument(); }); + +describe('ImageElement sanitization', () => { + it.each([['javascript:alert(1)'], ['JAVASCRIPT:alert(1)'], ['data:text/html,'], ['vbscript:msgbox(1)']])( + 'replaces dangerous %s scheme with "#" in image href and src', + (dangerousSrc) => { + render( + , + ); + + expect(screen.getByRole('link')).toHaveAttribute('href', '#'); + expect(screen.getByRole('img')).toHaveAttribute('src', '#'); + }, + ); + + it('preserves safe http(s) image URLs untouched', () => { + render( + , + ); + + expect(screen.getByRole('link')).toHaveAttribute('href', 'https://rocket.chat/logo.svg'); + expect(screen.getByRole('img')).toHaveAttribute('src', 'https://rocket.chat/logo.svg'); + }); +}); diff --git a/packages/gazzodown/src/elements/ImageElement.tsx b/packages/gazzodown/src/elements/ImageElement.tsx index e4c6bb08f772d..33d9b401e8fbd 100644 --- a/packages/gazzodown/src/elements/ImageElement.tsx +++ b/packages/gazzodown/src/elements/ImageElement.tsx @@ -2,6 +2,8 @@ import type * as MessageParser from '@rocket.chat/message-parser'; import type { ReactElement } from 'react'; import { useMemo } from 'react'; +import { sanitizeUrl } from './sanitizeUrl'; + const flattenMarkup = ( markup: | MessageParser.Timestamp @@ -47,10 +49,11 @@ type ImageElementProps = { const ImageElement = ({ src, alt }: ImageElementProps): ReactElement => { const plainAlt = useMemo(() => flattenMarkup(alt), [alt]); + const safeSrc = sanitizeUrl(src); return ( - - {plainAlt} + + {plainAlt} ); };