Lobe Chat Desktop Vulnerable to Remote Code Execution via XSS in Chat Messages
Description
Lobe Chat is an open-source artificial intelligence chat framework. Prior to version 1.129.4, there is a a cross-site scripting (XSS) vulnerability when handling chat message in lobe-chat that can be escalated to remote code execution on the user’s machine. In lobe-chat, when the response from the server is like <lobeArtifact identifier="ai-new-interpretation" ...> , it will be rendered with the lobeArtifact node, instead of the plain text. However, when the type of the lobeArtifact is image/svg+xml , it will be rendered as the SVGRender component, which internally uses dangerouslySetInnerHTML to set the content of the svg, resulting in XSS attack. Any party capable of injecting content into chat messages, such as hosting a malicious page for prompt injection, operating a compromised MCP server, or leveraging tool integrations, can exploit this vulnerability. This vulnerability is fixed in 1.129.4.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
@lobehub/chatnpm | < 1.129.4 | 1.129.4 |
Affected products
1Patches
19f044edd07ce🐛 fix: fix svg xss issue (#9313)
6 files changed · +153 −4
packages/utils/package.json+3 −1 modified@@ -14,9 +14,11 @@ "dependencies": { "@lobechat/const": "workspace:*", "@lobechat/types": "workspace:*", - "dayjs": "^1.11.18" + "dayjs": "^1.11.18", + "dompurify": "^3.2.7" }, "devDependencies": { + "@types/dompurify": "^3.2.0", "vitest-canvas-mock": "^0.3.3" } }
packages/utils/src/client/clipboard.ts+0 −0 renamedpackages/utils/src/client/index.ts+2 −0 modified@@ -1,2 +1,4 @@ +export * from './clipboard'; export * from './downloadFile'; export * from './exportFile'; +export * from './sanitize';
packages/utils/src/client/sanitize.test.ts+108 −0 added@@ -0,0 +1,108 @@ +import { describe, expect, it } from 'vitest'; + +import { sanitizeSVGContent } from './sanitize'; + +describe('sanitizeSVGContent', () => { + it('should preserve safe SVG elements and attributes', () => { + const safeSvg = ` + <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"> + <circle cx="50" cy="50" r="40" fill="red" stroke="blue" stroke-width="2" /> + <rect x="10" y="10" width="30" height="30" fill="green" /> + <path d="M10,20 L30,40" stroke="black" /> + </svg> + `; + + const sanitized = sanitizeSVGContent(safeSvg); + + expect(sanitized).toContain('<svg'); + expect(sanitized).toContain('xmlns="http://www.w3.org/2000/svg"'); + expect(sanitized).toContain('<circle'); + expect(sanitized).toContain('fill="red"'); + expect(sanitized).toContain('<rect'); + expect(sanitized).toContain('<path'); + }); + + it('should remove dangerous script tags', () => { + const maliciousSvg = ` + <svg xmlns="http://www.w3.org/2000/svg"> + <script>alert('XSS')</script> + <circle cx="50" cy="50" r="40" fill="red" /> + </svg> + `; + + const sanitized = sanitizeSVGContent(maliciousSvg); + + expect(sanitized).not.toContain('<script>'); + expect(sanitized).not.toContain('alert'); + expect(sanitized).toContain('<svg'); + }); + + it('should remove dangerous event handler attributes', () => { + const maliciousSvg = ` + <svg xmlns="http://www.w3.org/2000/svg"> + <circle cx="50" cy="50" r="40" fill="red" onclick="alert('click')" onload="alert('load')" /> + </svg> + `; + + const sanitized = sanitizeSVGContent(maliciousSvg); + + expect(sanitized).not.toContain('onclick'); + expect(sanitized).not.toContain('onload'); + expect(sanitized).toContain('<circle'); + expect(sanitized).toContain('fill="red"'); + }); + + it('should remove dangerous embed and object tags', () => { + const maliciousSvg = ` + <svg xmlns="http://www.w3.org/2000/svg"> + <object data="malicious.swf"></object> + <embed src="malicious.swf"></embed> + <circle cx="50" cy="50" r="40" fill="red" /> + </svg> + `; + + const sanitized = sanitizeSVGContent(maliciousSvg); + + // Note: DOMPurify with SVG profile may still allow some elements + // The key security protection is removing script and event handlers + expect(sanitized).toContain('<circle'); + expect(sanitized).toContain('fill="red"'); + }); + + it('should handle empty or invalid SVG content gracefully', () => { + expect(sanitizeSVGContent('')).toBe(''); + expect(sanitizeSVGContent('<invalid>content</invalid>')).toBe(''); + }); + + it('should preserve complex SVG structures while removing threats', () => { + const complexSvg = ` + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200"> + <defs> + <linearGradient id="grad1"> + <stop offset="0%" stop-color="red" /> + <stop offset="100%" stop-color="blue" /> + </linearGradient> + </defs> + <g transform="translate(50,50)"> + <script>malicious()</script> + <circle cx="50" cy="50" r="40" fill="url(#grad1)" onclick="hack()" /> + <text x="50" y="60" text-anchor="middle" onload="evil()">Hello</text> + </g> + </svg> + `; + + const sanitized = sanitizeSVGContent(complexSvg); + + // Should preserve safe elements and attributes + expect(sanitized).toEqual(` + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200"> + <defs> + <linearGradient id="grad1"> + <stop offset="0%" stop-color="red"></stop> + <stop offset="100%" stop-color="blue"></stop> + </linearGradient> + </defs> + <g transform="translate(50,50)"> + </g></svg>`); + }); +});
packages/utils/src/client/sanitize.ts+33 −0 added@@ -0,0 +1,33 @@ +import DOMPurify from 'dompurify'; + +/** + * Sanitizes SVG content to prevent XSS attacks while preserving safe SVG elements and attributes + * @param content - The SVG content to sanitize + * @returns Sanitized SVG content safe for rendering + */ +export const sanitizeSVGContent = (content: string): string => { + return DOMPurify.sanitize(content, { + FORBID_ATTR: [ + 'onblur', + 'onchange', + 'onclick', + 'onerror', + 'onfocus', + 'onkeydown', + 'onkeypress', + 'onkeyup', + 'onload', + 'onmousedown', + 'onmouseout', + 'onmouseover', + 'onmouseup', + 'onreset', + 'onselect', + 'onsubmit', + 'onunload', + ], + FORBID_TAGS: ['embed', 'link', 'object', 'script', 'style'], + KEEP_CONTENT: false, + USE_PROFILES: { svg: true, svgFilters: true }, + }); +};
src/features/Portal/Artifacts/Body/Renderer/SVG.tsx+7 −3 modified@@ -1,15 +1,16 @@ +import { copyImageToClipboard, sanitizeSVGContent } from '@lobechat/utils/client'; import { Button, Dropdown, Tooltip } from '@lobehub/ui'; import { App, Space } from 'antd'; import { css, cx } from 'antd-style'; import { CopyIcon, DownloadIcon } from 'lucide-react'; import { domToPng } from 'modern-screenshot'; +import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { Center, Flexbox } from 'react-layout-kit'; import { BRANDING_NAME } from '@/const/branding'; import { useChatStore } from '@/store/chat'; import { chatPortalSelectors } from '@/store/chat/selectors'; -import { copyImageToClipboard } from '@/utils/clipboard'; const svgContainer = css` width: 100%; @@ -36,6 +37,9 @@ const SVGRenderer = ({ content }: SVGRendererProps) => { const { t } = useTranslation('portal'); const { message } = App.useApp(); + // Sanitize SVG content to prevent XSS attacks + const sanitizedContent = useMemo(() => sanitizeSVGContent(content), [content]); + const generatePng = async () => { return domToPng(document.querySelector(`#${DOM_ID}`) as HTMLDivElement, { features: { @@ -50,7 +54,7 @@ const SVGRenderer = ({ content }: SVGRendererProps) => { let dataUrl = ''; if (type === 'png') dataUrl = await generatePng(); else if (type === 'svg') { - const blob = new Blob([content], { type: 'image/svg+xml' }); + const blob = new Blob([sanitizedContent], { type: 'image/svg+xml' }); dataUrl = URL.createObjectURL(blob); } @@ -73,7 +77,7 @@ const SVGRenderer = ({ content }: SVGRendererProps) => { > <Center className={cx(svgContainer)} - dangerouslySetInnerHTML={{ __html: content }} + dangerouslySetInnerHTML={{ __html: sanitizedContent }} id={DOM_ID} /> <Flexbox className={cx(actions)}>
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
9- github.com/advisories/GHSA-m79r-r765-5f9jghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-59417ghsaADVISORY
- github.com/lobehub/lobe-chat/blob/0a1dcf943ea294e35acbe57d07f7974efede8e2e/apps/desktop/src/main/controllers/SystemCtr.tsghsaWEB
- github.com/lobehub/lobe-chat/blob/0a1dcf943ea294e35acbe57d07f7974efede8e2e/src/features/Conversation/components/MarkdownElements/LobeArtifact/index.tsghsaWEB
- github.com/lobehub/lobe-chat/blob/0a1dcf943ea294e35acbe57d07f7974efede8e2e/src/features/Conversation/components/MarkdownElements/LobeArtifact/rehypePlugin.tsghsaWEB
- github.com/lobehub/lobe-chat/blob/0a1dcf943ea294e35acbe57d07f7974efede8e2e/src/features/Portal/Artifacts/Body/Renderer/SVG.tsxghsaWEB
- github.com/lobehub/lobe-chat/blob/0a1dcf943ea294e35acbe57d07f7974efede8e2e/src/features/Portal/Artifacts/Body/Renderer/index.tsxghsaWEB
- github.com/lobehub/lobe-chat/commit/9f044edd07ce102fe9f4b2fb47c62191c36da05cghsax_refsource_MISCWEB
- github.com/lobehub/lobe-chat/security/advisories/GHSA-m79r-r765-5f9jghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.