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)