{"id":"CVE-2026-54070","aliases":["GHSA-w7cg-whh7-xp28"],"title":"SiYuan: Stored XSS in Bazaar marketplace via package README event handlers","summary":"SiYuan: Stored XSS in Bazaar marketplace via package README event handlers","severity":"high","cvss":7.1,"cwe":["CWE-79","CWE-184"],"vendor":"siyuan-note","product":"github.com/siyuan-note/siyuan/kernel","ecosystem":"go","affected":["github.com/siyuan-note/siyuan/kernel < 0.0.0-20260628153353-2d5d72223df4"],"patched":["github.com/siyuan-note/siyuan/kernel 0.0.0-20260628153353-2d5d72223df4"],"published":"2026-07-10","updated":"2026-07-10","source":"GHSA","sourceUrl":"https://github.com/advisories/GHSA-w7cg-whh7-xp28","references":[{"url":"https://github.com/siyuan-note/siyuan/security/advisories/GHSA-w7cg-whh7-xp28"},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-54070"},{"url":"https://github.com/advisories/GHSA-w7cg-whh7-xp28"}],"tags":["ghsa","go"],"epss":0.0018,"epssPercentile":0.07746,"ingestedAt":"2026-07-10T20:06:10.797Z","slug":"CVE-2026-54070","body":"## Overview\n\n## Summary\n\n`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.\n\nThat 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.\n\nThe 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.\n\nThe README is rendered when an Administrator opens a package in Settings → Marketplace, after the one-time marketplace trust consent. Install is not required.\n\nResult: 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.\n\n## Affected\n\nsiyuan-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.\nCondition: 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`.\nBoth the online browse path (`getBazaarPackageREADME`) and the installed-package path (`getInstalledPlugin`) reach the same sink.\n\n## Root cause\n\n`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.\n`render/sanitizer.go:235-334` (lute): `eventAttrs` is the w3schools handler list and contains no pointer, beforetoggle, focusin, animation, or transition handlers.\n`kernel/bazaar/readme.go:108-118`: `renderPackageREADME` builds the engine with `SetSanitize(true)` and returns the HTML string to the caller.\n`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`).\n`app/src/config/bazaar.ts:600` and `:609`: `mdElement.innerHTML = data.preferredReadme` / `= response.data.html`, no DOMPurify, target is a plain div.\nKernel HTTP responses carry no CSP/X-Frame-Options/X-Content-Type-Options header (live-confirmed), so an inline handler is not blocked.\n\n## Reproduction\n\n`b3log/siyuan:v3.6.5` Docker, default config, access auth code set, marketplace trust accepted.\n\n1. Place a package whose README carries a non-blocklisted handler (an online community package produces the identical render at browse time):\n\n```\nmkdir -p workspace/data/plugins/evil-plugin\ncat > workspace/data/plugins/evil-plugin/plugin.json <<'JSON'\n{\"name\":\"evil-plugin\",\"author\":\"x\",\"version\":\"1.0.0\",\"minAppVersion\":\"3.0.0\",\n \"displayName\":{\"default\":\"Evil\"},\"description\":{\"default\":\"poc\"},\n \"readme\":{\"default\":\"README.md\"},\"backends\":[\"all\"],\"frontends\":[\"all\"]}\nJSON\nprintf '<div onpointerover=\"alert(document.domain)\">plugin description</div>\\n' \\\n  > workspace/data/plugins/evil-plugin/README.md\n```\n\n2. Request the rendered README the way the Marketplace panel does:\n\n```\ncurl -s -X POST http://127.0.0.1:6806/api/bazaar/getInstalledPlugin \\\n  -H \"Authorization: Token <API-TOKEN>\" -H \"Content-Type: application/json\" \\\n  -d '{\"frontend\":\"all\",\"keyword\":\"\"}'\n```\n\nResponse `data.packages[].preferredReadme` contains the handler verbatim:\n\n```\n<div onpointerover=\"alert(document.domain)\">plugin description</div>\n```\n\nA control `<img src=x onerror=...>` in the same README is returned HTML-escaped and inert.\n\n3. In Settings → Marketplace, open the package and move the pointer over its README.\n\nLive-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.\n\n## Impact\n\n- JavaScript execution in the Administrator's authenticated origin on a marketplace package view plus one hover/click/focus, no install needed.\n- Theft of the kernel API token (`conf.api.token`), which grants full Administrator API access.\n- Pivot to `installBazaarPlugin` and kernel control; the runtime image ships a shell.\n- A single malicious community package reaches every instance that views its listing.\n\n## Credit\n\nJan Kahmen, [turingpoint](https://turingpoint.de) (jan@turingpoint.de)\n\n## Affected packages\n\n- `github.com/siyuan-note/siyuan/kernel < 0.0.0-20260628153353-2d5d72223df4`\n\n## Remediation\n\nUpgrade to a patched release:\n\n- `github.com/siyuan-note/siyuan/kernel 0.0.0-20260628153353-2d5d72223df4`","depth":"twilight","depthScore":39,"depthScoreParts":{"impact":39.1,"likelihood":0,"exploitation":0,"ransomware":0},"changes":[]}