CVE-2026-50551: SiYuan Stored XSS to RCE via Attribute View Asset Cell
A stored XSS flaw in SiYuan's database asset cell renderer lets an attacker plant a malicious link that executes arbitrary code on the victim's desktop the moment they open a shared note.
The problem
SiYuan v3.6.5 and earlier render Attribute View asset cell content using `escapeAttr()`, which only escapes quote characters, leaving `<` and `>` untouched. Raw `item.name` and `item.content` are also concatenated directly into `innerHTML` with zero escaping in `cell.ts:1008` and `blockAttr.ts:93`.
The Electron renderer runs with `nodeIntegration: true` and `contextIsolation: false`, so any JavaScript that executes there can call `require('child_process')` directly. A victim opening or even hovering over a poisoned asset cell triggers full RCE. In a sync or collaboration setup the payload propagates to every user automatically via `.sy` files.
Proof of concept
A working proof-of-concept for CVE-2026-50551 in github.com/siyuan-note/siyuan/kernel, with the exact payload below.
<!-- Sink 1: place this in an AV asset cell "Link" field (triggers on page load) -->
<img src=x onerror=alert(document.domain)>
<!-- RCE variant on Electron desktop -->
<img src=x onerror=require('child_process').exec('calc')>
<!-- Sink 2: hover-triggered via aria-label round-trip -->
<!-- escapeAttr() leaves < > intact, so the same payload in the Link field
also lands in aria-label; popover.ts reads it via getAttribute,
passes it through decodeURIComponent, then assigns to innerHTML -->
<img src=x onerror=require('child_process').exec('calc')>The root cause is two distinct sinks in the AV cell renderer. In `cell.ts:1008` the template literal closes the attribute and writes `>${item.name || item.content}</span>` with no escaping at all, so a payload like `<img src=x onerror=...>` is emitted verbatim into the DOM.
In `blockAttr.ts:93` the `aria-label` attribute uses only `escapeAttr()` (escapes `"` and `'`, not `<` or `>`), and the text content is also unescaped.
The hover sink works because `popover.ts` reads `aria-label` via `getAttribute` (which decodes HTML entities), runs the value through `decodeURIComponent`, then sets `messageElement.innerHTML`, completing a classic attribute-to-innerHTML round-trip. The patch replaces `escapeAttr()` with `escapeAriaLabel()` on every `aria-label` attribute and wraps all text-content interpolations with `escapeHtml()`, closing both sinks.
The fix
Upgrade to SiYuan 3.7.0 (commit 2d5d72223df4). The patch replaces `escapeAttr()` with `escapeAriaLabel()` for all `aria-label` attributes in AV cell renderers (`cell.ts`, `blockAttr.ts`) and wraps `item.name` and `item.content` with `escapeHtml()` before concatenation into element text content.
Long-term hardening: set `contextIsolation: true` and `nodeIntegration: false` in Electron's `BrowserWindow` options to eliminate the entire XSS-to-RCE escalation path.
Reported by Yunkaiwjs.
Related research
- critical · 9.9CVE-2026-54067CVE-2026-54067: SiYuan Stored XSS to RCE via CSS Snippet Style Tag Breakout
- high · 7.1CVE-2026-54070CVE-2026-54070: SiYuan Stored XSS via Bazaar README Event Handler Bypass
- critical · 9.9CVE-2026-54158CVE-2026-54158: SiYuan Stored XSS to RCE via Unescaped Attribute-View Cell Rendering
- high · 7.5CVE-2026-54066CVE-2026-54066: SiYuan Unauthenticated Path Traversal via Double URL Encoding in /assets/ (Publish Mode)