Skip to main content

Boteraser | Website and Server Security Solutions

🛡️ CVE-2026-40922 — siyuan

🟡 CVSS 5.4 — Medium ✅ No Known Exploit CWE-79 NVD
5.4
CVSS Score
0 Low4 Medium7 High9 Critical10

Description

SiYuan has incomplete fix for CVE-2026-33066: XSS

Summary

The incomplete fix for SiYuan's bazaar README rendering enables the Lute HTML sanitizer but fails to block <iframe> tags, allowing stored XSS via srcdoc attributes containing embedded scripts that execute in the Electron context.

Affected Package

  • Ecosystem: Go
  • Package: github.com/siyuan-note/siyuan
  • Affected versions: < commit b382f50e1880
  • Patched versions: >= commit b382f50e1880

Details

The renderPackageREADME() function in kernel/bazaar/readme.go renders Markdown README content from bazaar (marketplace) packages into HTML. The original vulnerability allowed stored XSS through unsanitized HTML in READMEs. The fix adds luteEngine.SetSanitize(true) to enable Lute's built-in HTML sanitizer.

However, the Lute sanitizer in lute/render/sanitizer.go has a critical gap:

1. <iframe> is explicitly commented out of setOfElementsToSkipContent, so iframe tags pass through.

2. The srcdoc attribute is checked against URL-prefix blocklists (javascript:, data:text/html), but srcdoc contains raw HTML content, not a URL. A value like <img src=x onerror=alert(1)> does not start with any blocked prefix.

3. The browser renders srcdoc HTML in a nested browsing context, executing embedded scripts and event handlers.

The fix correctly blocks direct <script> tags, event handler attributes, and javascript: protocol links. However:

  • <iframe srcdoc="<script>alert(document.domain)</script>"> passes through because iframe is not blocked and the srcdoc value is raw HTML (not a URL scheme).
  • <iframe srcdoc="<img src=x onerror=alert(document.cookie)>"> also passes because the event handler is inside the srcdoc string value, not a top-level tag attribute.

PoC

```python

"""

CVE-2026-33066 - Incomplete Sanitization in SiYuan Bazaar README Rendering

Component: kernel/bazaar/readme.go :: renderPackageREADME()

Patch: https://github.com/siyuan-note/siyuan/commit/b382f50e1880ed996364509de5a10a72d7409428

"""

import re

import sys

from html.parser import HTMLParser

ELEMENTS_TO_SKIP_CONTENT = {

"frame", "frameset",

# "iframe", # NOTE: iframe is commented out in the original Go code!

"noembed", "noframes", "noscript", "nostyle",

"object", "script", "style", "title",

}

EVENT_ATTRS = {

"onafterprint", "onbeforeprint", "onbeforeunload", "onerror",

"onhashchange", "onload", "onmessage", "onoffline", "ononline",

"onpagehide", "onpageshow", "onpopstate", "onresize", "onstorage",

"onunload", "onblur", "onchange", "oncontextmenu", "onfocus",

"oninput", "oninvalid", "onreset", "onsearch", "onselect",

"onsubmit", "onkeydown", "onkeypress", "onkeyup", "onclick",

"ondblclick", "onmousedown", "onmousemove", "onmouseout",

"onmouseover", "onmouseleave", "onmouseenter", "onmouseup",

"onmousewheel", "onwheel", "ondrag", "ondragend", "ondragenter",

"ondragleave", "ondragover", "ondragstart", "ondrop", "onscroll",

"oncopy", "oncut", "onpaste", "onabort", "oncanplay",

"oncanplaythrough", "oncuechange", "ondurationchange", "onemptied",

"onended", "onloadeddata", "onloadedmetadata", "onloadstart",

"onpause", "onplay", "onplaying", "onprogress", "onratechange",

"onseeked", "onseeking", "onstalled", "onsuspend", "ontimeupdate",

"onvolumechange", "onwaiting", "ontoggle", "onbegin", "onend",

"onrepeat", "http-equiv", "formaction",

}

URL_ATTRS = {"src", "srcdoc", "srcset", "href"}

BLOCKED_URL_PREFIXES = ("data:image/svg+xml", "data:text/html", "javascript")

SELF_CLOSING_TAGS = {"img", "br", "hr", "input", "meta", "link", "area",

"base", "col", "embed", "source", "track", "wbr"}

def sanitize_attr_value_for_url(key, val):

cleaned = val.lower().strip()

cleaned = ''.join(c for c in cleaned if not c.isspace() or c == ' ')

for prefix in BLOCKED_URL_PREFIXES:

if cleaned.startswith(prefix):

return False

return True

class LuteSanitizer(HTMLParser):

def __init__(self):

super().__init__(convert_charrefs=False)

self.output = []

self.skip_depth = 0

def handle_starttag(self, tag, attrs):

tag = tag.lower()

if tag in ELEMENTS_TO_SKIP_CONTENT:

self.skip_depth += 1

self.output.append(" ")

return

if self.skip_depth > 0:

return

sanitized_attrs = []

for key, val in attrs:

key = key.lower()

if val is None: val = ""

if key in EVENT_ATTRS: continue

if key in URL_ATTRS:

if not sanitize_attr_value_for_url(key, val): continue

sanitized_attrs.append((key, val))

parts = ["<" + tag]

for key, val in sanitized_attrs:

escaped_val = val.replace("&", "&").replace('"', """)

parts.append(f' {key}="{escaped_val}"')

if tag in SELF_CLOSING_TAGS: parts.append(" /")

parts.

How this vulnerability can be exploited

This issue can be reached over the network, attack complexity is low, an attacker needs low-level privileges on the target. A user must be tricked into taking some action. The scope is changed, meaning a successful attack can affect components beyond the vulnerable one. Rated impact: confidentiality low, integrity low, availability none.

CVSS metrics in full

The score comes from this vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N

  • Attack vector: Network — reachable from anywhere that can route to the service.
  • Attack complexity: Low — the attack works reliably, with no preparation.
  • Privileges required: Low — an ordinary user account is enough.
  • User interaction: Required — someone has to click, open or visit something.
  • Scope: Changed — a successful attack reaches components beyond the vulnerable one.
  • Confidentiality impact: Low — limited, and the attacker does not choose what is affected.
  • Integrity impact: Low — limited, and the attacker does not choose what is affected.
  • Availability impact: None.

Weakness class

CVE-2026-40922 is classified as CWE-79: Cross-site Scripting (XSS). User-supplied data is written into a page without escaping, so attacker script runs in the browser of anyone who views it.

Affected software

CVE-2026-40922 is recorded against 2 packages.

  • github.com/siyuan-note/siyuan/kernel
  • siyuan (from 3.6.1 up to 3.6.4)

Timeline and source

Published on 25 June 2026 and last revised on 8 July 2026. No public exploit is currently recorded for this entry. A vendor advisory or fix has been published. Record sourced from NVD.

References

github.com (Advisory)
nvd.nist.gov (Advisory)
github.com (Web)
github.com (Web)
github.com (Web)
nvd.nist.gov (Web)

Other advisories for this package

github.com/siyuan-note/siyuan/kernel has other advisories on record. If you are patching this one, these are worth checking on the same host:

Same weakness in other software

These advisories are the same class of weakness (CWE-79: Cross-site Scripting (XSS)) in other software:

Details

Severity Medium
CVSS Score 5.4
CVSS Vector CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N
CWE CWE-79
Public Exploit ✅ No
Source NVD
Published 2026-06-25
Updated 2026-08-20
Modified 2026-07-08

Affected Packages

Software From version Fixed in
github.com/siyuan-note/siyuan/kernel
siyuan 3.6.1 3.6.4

Similar Threats

Vulnerability Monitoring

Track new vulnerabilities in siyuan

CVE-2026-40922 is rated CVSS 5.4 Medium. BotEraser monitors your WordPress installation and notifies you when software you use appears in our vulnerability database.

Set Up Free Alerts →

No credit card required  ·  Results in minutes

ⓘ Data Notice: The information presented above has been compiled from publicly available internet sources. Boteraser aggregates this data solely for informational purposes and does not independently classify, evaluate, or endorse any findings about the vulnerabilities listed. The accuracy and completeness of this information is the sole responsibility of the original publishers. Boteraser and its operators accept no liability for any decisions made based on this data.

Browse related advisories

All advisoriesCVECVE 2026