Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🐹
🐹 Go
Not in CISA KEV
HIGH severity

SiYuan: Stored XSS in Bazaar marketplace via package README event handlersGHSA-w7cg-whh7-xp28

HIGH

GHSA-w7cg-whh7-xp28 is a high-severity (CVSS 7.1) Cross-site Scripting (XSS) vulnerability in github.com/siyuan-note/siyuan/kernel. A fix is available for github.com/siyuan-note/siyuan/kernel — see the affected versions and patch details below.

Also known asCVE-2026-54070GO-2026-5965
Published
Updated
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Oct 23, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

No confirmed exploitation observed yet

  • A successful exploit gives an attacker total control of the affected component, not partial access.
  • CISA’s own triage has not observed active exploitation or public proof-of-concept code for this CVE as of its last assessment.

Exploitation and automatability from CISA’s SSVC triage for GHSA-w7cg-whh7-xp28.

EPSS Exploitation Probability

via FIRST.org ↗
0.3%probability of exploitation in next 30 days
Lower Risk0.00%
Lower risk than most CVEs22th percentile — riskier than 22% of all scored CVEsHighest risk
0.00%0.27%0.53%0.80%0.2%0.2%0.3%0.3%0.3%Jul 26Sep 26Oct 26

Probability of exploitation in the next 30 days, from FIRST.org EPSS.

How urgent is this, really

GHSA-w7cg-whh7-xp28 by exploitation likelihood (EPSS) against impact (CVSS). Outside the shaded patch-first corner.

Where this sits among everything scored

Of 394,803 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Counts from FIRST.org, log-scaled.

Real-World Exposure

1 pkg affected
🐹github.com/siyuan-note/siyuan/kernel

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects Go packages — download data is not available via public APIs for these ecosystems.

Description

Summary

renderPackageREADME in kernel/bazaar/readme.go renders a Bazaar package README from Markdown to HTML with the lute engine and SetSanitize(true). The lute sanitizer is an event-handler blocklist: allowAttr rejects only attribute names present in a fixed eventAttrs map copied from the w3schools legacy handler list.

That map omits modern event handlers. onpointerover, onpointerdown, onauxclick, onbeforetoggle, onfocusin, onanimationstart, and ontransitionend are not in the list, so the sanitizer passes them through verbatim on any tag.

The frontend assigns the rendered HTML to mdElement.innerHTML in app/src/config/bazaar.ts with no client-side DOMPurify on this path, into a normal element in the main document (no iframe, no sandbox). The kernel sends no Content-Security-Policy, X-Frame-Options, or X-Content-Type-Options header on any response, so an inline handler runs when its event fires.

The README is rendered when an Administrator opens a package in Settings → Marketplace, after the one-time marketplace trust consent. Install is not required.

Result: a third-party Bazaar package author runs JavaScript in the Administrator's authenticated SiYuan origin when the Administrator views and interacts with the package listing, and gains full control of the workspace.

Affected

siyuan-note/siyuan, <= 3.6.5 (latest release, 2026-04-21). Confirmed live-exploitable on the b3log/siyuan:v3.6.5 image; identical code on master HEAD. Condition: the Administrator has accepted the marketplace trust consent (bazaar.trust, default false) and browses community Bazaar packages. The lute dependency pin is github.com/88250/lute v1.7.7-0.20260419134724-bb68012f231d. Both the online browse path (getBazaarPackageREADME) and the installed-package path (getInstalledPlugin) reach the same sink.

Root cause

render/sanitizer.go:225-232 (lute): allowAttr(name) returns false only when name exists in the eventAttrs map, an attribute denylist rather than an allowlist. render/sanitizer.go:235-334 (lute): eventAttrs is the w3schools handler list and contains no pointer, beforetoggle, focusin, animation, or transition handlers. kernel/bazaar/readme.go:108-118: renderPackageREADME builds the engine with SetSanitize(true) and returns the HTML string to the caller. kernel/bazaar/readme.go:48-88: GetBazaarPackageREADME renders an untrusted remote package README; kernel/api/bazaar.go exposes it at /api/bazaar/getBazaarPackageREADME (router.go:423, CheckAuth). app/src/config/bazaar.ts:600 and :609: mdElement.innerHTML = data.preferredReadme / = response.data.html, no DOMPurify, target is a plain div. Kernel HTTP responses carry no CSP/X-Frame-Options/X-Content-Type-Options header (live-confirmed), so an inline handler is not blocked.

Reproduction

b3log/siyuan:v3.6.5 Docker, default config, access auth code set, marketplace trust accepted.

  1. Place a package whose README carries a non-blocklisted handler (an online community package produces the identical render at browse time):
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' \
  > workspace/data/plugins/evil-plugin/README.md
  1. Request the rendered README the way the Marketplace panel does:
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":""}'

Response data.packages[].preferredReadme contains the handler verbatim:

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

A control <img src=x onerror=...> in the same README is returned HTML-escaped and inert.

  1. In Settings → Marketplace, open the package and move the pointer over its README.

Live-verified: the rendered HTML is assigned to mdElement.innerHTML (no CSP, no sandbox) and the onpointerover handler executes alert(document.domain) in the SiYuan origin on hover. Handlers do not auto-fire on insertion; one pointer/focus/click interaction on the listing triggers them.

Impact

  • JavaScript execution in the Administrator's authenticated origin on a marketplace package view plus one hover/click/focus, no install needed.
  • Theft of the kernel API token (conf.api.token), which grants full Administrator API access.
  • Pivot to installBazaarPlugin and kernel control; the runtime image ships a shell.
  • A single malicious community package reaches every instance that views its listing.

Credit

Jan Kahmen, turingpoint ([email protected])

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
🐹Gogithub.com/siyuan-note/siyuan/kernelall versions0.0.0-20260628153353-2d5d72223df4go get github.com/siyuan-note/siyuan/kernel@v0.0.0-20260628153353-2d5d72223df4

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for github.com/siyuan-note/siyuan/kernel, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.

  2. Fix

    Update github.com/siyuan-note/siyuan/kernel to 0.0.0-20260628153353-2d5d72223df4 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-w7cg-whh7-xp28 is resolved across your whole dependency graph.

  3. Workarounds

    Escape or sanitise the affected output on the server side rather than relying on client-side filtering, and add a Content-Security-Policy that blocks inline script execution so injected markup cannot run even if it reaches the page.

Frequently Asked Questions

## Summary `renderPackageREADME` in `kernel/bazaar/readme.go` renders a Bazaar package README from Markdown to HTML with the lute engine and `SetSanitize(true)`. The lute sanitizer is an event-handler blocklist: `allowAttr` rejects only attribute names present in a fixed `eventAttrs` map copied from the w3schools legacy handler list. That map omits modern event handlers. `onpointerover`, `onpointerdown`, `onauxclick`, `onbeforetoggle`, `onfocusin`, `onanimationstart`, and `ontransitionend` are not in the list, so the sanitizer passes them through verbatim on any tag. The frontend assigns th
O3 Security · Impact-Aware SCA

Is GHSA-w7cg-whh7-xp28 in your dependencies?

Find it across Go, including transitive dependencies.

SiYuan: Stored XSS in Bazaar marketplace via package…