GHSA-5hc8-qmg8-pw27
SiYuan has a SVG Sanitizer Bypass via `<animate>` Element — Unauthenticated XSS
EPSS Exploitation Probability
EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.
Blast Radius
github.com/siyuan-note/siyuan/kernelReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects Go packages — download data is not available via public APIs for these ecosystems.
Description
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.
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 🐹Go | github.com/siyuan-note/siyuan/kernel | all versions | 0.0.0-20260310025236-297bd526708f |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for github.com/siyuan-note/siyuan/kernel. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.
Fix
Update github.com/siyuan-note/siyuan/kernel to 0.0.0-20260310025236-297bd526708f or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-5hc8-qmg8-pw27 is resolved across your whole dependency graph.
Workarounds
If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.
How O3 protects you
O3 pinpoints whether GHSA-5hc8-qmg8-pw27 is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.
Tailored to GHSA-5hc8-qmg8-pw27. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.
Frequently Asked Questions
Is GHSA-5hc8-qmg8-pw27 in your dependencies?
O3 detects GHSA-5hc8-qmg8-pw27 across Go dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.