The problem in depth
The default chat message-part renderers in @mui/x-chat-headless write the untrusted part.url straight into an anchor href with no sanitization. Message parts come from assistant/model output, tool results, or RAG source data, so a javascript: (or data:) URL flows unmodified into the link and becomes clickable.
Affected sinks:
packages/x-chat-headless/src/message/defaultMessagePartRenderers.tsx (renderDefaultFilePart non-image anchor, and renderDefaultSourceUrlPart anchor)
packages/x-chat-headless/src/message/parts/SourceUrlPart.tsx
packages/x-chat-headless/src/message/parts/FilePart.tsx
The package already ships a sanitizer, safeUri() in packages/x-chat-headless/src/message/parts/partUtils.ts, which whitelists http/https/mailto/tel and is unit-tested, but none of the renderers call it.
React version matters here, and the package supports react "^17.0.0 || ^18.0.0 || ^19.0.0":
- React 17 and 18 render
javascript: URLs as-is, so the link executes the script on click.
- React 19 neutralizes
javascript: URLs at render time, so that specific vector does not fire there.
- No React version blocks
data: URLs at render time.
So apps on React 17 or 18 are exposed, and relying on a React 19 only protection is fragile when the package advertises React 17/18 support. safeUri also closes the data: vector at the library level.
Steps to reproduce
Standalone proof of how the URL reaches the DOM on each supported React version:
const React = require('react');
const { renderToStaticMarkup } = require('react-dom/server');
console.log(
renderToStaticMarkup(
React.createElement('a', { href: 'javascript:alert(document.cookie)' }, 'Click me'),
),
);
// React 17.0.2 / 18.3.1: <a href="javascript:alert(document.cookie)">Click me</a> (rendered as-is)
// React 19.x: href replaced by React with a harmless throw sentinel
In the component, on a React 17 or 18 app:
- Render a chat with the default renderers (
ChatRoot + MessageRoot + MessageContent).
- Provide an assistant message with a
source-url part:
{ type: 'source-url', sourceId: 's1', url: 'javascript:alert(document.cookie)', title: 'Click me' }
- The rendered anchor is
<a href="javascript:alert(document.cookie)">Click me</a>. Clicking it runs the script in the app origin.
A file part with a javascript: url reproduces the same through its anchor.
Current behavior
part.url is used directly as the href of the rendered anchor in the three files above, with no sanitization. On React 17/18 a javascript: URL is clickable and executes.
Expected behavior
Untrusted message-part URLs should be sanitized before being used as a link href, independent of the host React version. A javascript:/data: URL should produce an inert link. The existing safeUri() helper already implements the right policy.
Context
Building a chat UI with the @mui/x-chat-headless default renderers and showing model/tool/RAG provided source links. The suggested fix is to wrap part.url with the existing safeUri() in the anchor href of the three renderers. Image src should be left unchanged so legitimate data:image and blob: previews keep working, and because a javascript: value in <img src> does not execute.
Your environment
Reproduced against the package source on the current master, and verified how the URL reaches the DOM with react-dom/server renderToStaticMarkup on React 17.0.2, 18.3.1, and 19.2.7.
@mui/x-chat-headless: current master
react / react-dom: 17.0.2, 18.3.1, 19.2.7
Search keywords: chat, x-chat, x-chat-headless, sanitize, javascript URL, href, safeUri, message part, source-url, XSS
The problem in depth
The default chat message-part renderers in
@mui/x-chat-headlesswrite the untrustedpart.urlstraight into an anchorhrefwith no sanitization. Message parts come from assistant/model output, tool results, or RAG source data, so ajavascript:(ordata:) URL flows unmodified into the link and becomes clickable.Affected sinks:
packages/x-chat-headless/src/message/defaultMessagePartRenderers.tsx(renderDefaultFilePartnon-image anchor, andrenderDefaultSourceUrlPartanchor)packages/x-chat-headless/src/message/parts/SourceUrlPart.tsxpackages/x-chat-headless/src/message/parts/FilePart.tsxThe package already ships a sanitizer,
safeUri()inpackages/x-chat-headless/src/message/parts/partUtils.ts, which whitelistshttp/https/mailto/teland is unit-tested, but none of the renderers call it.React version matters here, and the package supports
react "^17.0.0 || ^18.0.0 || ^19.0.0":javascript:URLs as-is, so the link executes the script on click.javascript:URLs at render time, so that specific vector does not fire there.data:URLs at render time.So apps on React 17 or 18 are exposed, and relying on a React 19 only protection is fragile when the package advertises React 17/18 support.
safeUrialso closes thedata:vector at the library level.Steps to reproduce
Standalone proof of how the URL reaches the DOM on each supported React version:
In the component, on a React 17 or 18 app:
ChatRoot+MessageRoot+MessageContent).source-urlpart:<a href="javascript:alert(document.cookie)">Click me</a>. Clicking it runs the script in the app origin.A
filepart with ajavascript:urlreproduces the same through its anchor.Current behavior
part.urlis used directly as thehrefof the rendered anchor in the three files above, with no sanitization. On React 17/18 ajavascript:URL is clickable and executes.Expected behavior
Untrusted message-part URLs should be sanitized before being used as a link
href, independent of the host React version. Ajavascript:/data:URL should produce an inert link. The existingsafeUri()helper already implements the right policy.Context
Building a chat UI with the
@mui/x-chat-headlessdefault renderers and showing model/tool/RAG provided source links. The suggested fix is to wrappart.urlwith the existingsafeUri()in the anchorhrefof the three renderers. Imagesrcshould be left unchanged so legitimatedata:imageandblob:previews keep working, and because ajavascript:value in<img src>does not execute.Your environment
Reproduced against the package source on the current
master, and verified how the URL reaches the DOM withreact-dom/serverrenderToStaticMarkupon React 17.0.2, 18.3.1, and 19.2.7.Search keywords: chat, x-chat, x-chat-headless, sanitize, javascript URL, href, safeUri, message part, source-url, XSS