Moderate severityOSV Advisory· Published Jan 16, 2026· Updated Jan 16, 2026
SiYuan Vulnerable to Stored Cross-Site Scripting (XSS) via Unrestricted SVG File Upload
CVE-2026-23645
Description
SiYuan is self-hosted, open source personal knowledge management software. Prior to 3.5.4-dev2, a Stored Cross-Site Scripting (XSS) vulnerability exists in SiYuan Note. The application does not sanitize uploaded SVG files. If a user uploads and views a malicious SVG file (e.g., imported from an untrusted source), arbitrary JavaScript code is executed in the context of their authenticated session. This vulnerability is fixed in 3.5.4-dev2.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/siyuan-note/siyuan/kernelGo | < 0.0.0-20260116101155-11115da3d0de | 0.0.0-20260116101155-11115da3d0de |
Affected products
1- Range: dev2.0.17-2, v0.1.0, v0.1.1, …
Patches
111115da3d0de:lock: Do not execute scripts in assets SVG by default to prevent XSS https://github.com/siyuan-note/siyuan/issues/16844
23 files changed · +125 −3
app/appearance/langs/ar_SA.json+2 −0 modified@@ -128,6 +128,8 @@ "click": "نقر", "allowHTMLBLockScript": "السماح بتنفيذ البرامج النصية في كتل HTML", "allowHTMLBLockScriptTip": "بعد التمكين، لن يتم تصحيح البرنامج النصي في كتلة HTML، يرجى إدراك المخاطر المحتملة لهجمات XSS", + "allowSVGScript": "السماح بتشغيل السكربتات داخل SVG", + "allowSVGScriptTip": "عند التفعيل، لن يتم تصفية الكود داخل SVG لأغراض الأمان. يرجى الانتباه إلى مخاطر XSS المحتملة", "autoLaunchMode0": "عدم التشغيل تلقائيًا", "autoLaunchMode1": "التشغيل التلقائي بعد الإقلاع", "autoLaunchMode2": "التشغيل التلقائي وتقليل الواجهة الرئيسية بعد الإقلاع",
app/appearance/langs/de_DE.json+2 −0 modified@@ -128,6 +128,8 @@ "click": "Klick", "allowHTMLBLockScript": "Die Ausführung von Skripten innerhalb von HTML-Blöcken zulassen", "allowHTMLBLockScriptTip": "Nach der Aktivierung wird das Skript im HTML-Block nicht bereinigt. Bitte seien Sie sich des potenziellen Risikos von XSS-Angriffen bewusst.", + "allowSVGScript": "Ausführen von Skripten innerhalb von SVG erlauben", + "allowSVGScriptTip": "Wenn aktiviert, wird der Code im SVG nicht sicherheitsgefiltert。Achten Sie auf mögliche XSS-Risiken", "autoLaunchMode0": "Nicht automatisch starten", "autoLaunchMode1": "Automatisch nach dem Booten starten", "autoLaunchMode2": "Automatisch starten und die Hauptoberfläche minimieren nach dem Booten",
app/appearance/langs/en_US.json+2 −0 modified@@ -128,6 +128,8 @@ "click": "Click", "allowHTMLBLockScript": "Allow execution of scripts within HTML blocks", "allowHTMLBLockScriptTip": "When enabled, the script in the HTML block will not be sanitized, Please be aware of the potential risk of XSS attacks", + "allowSVGScript": "Allow execution of scripts inside SVG", + "allowSVGScriptTip": "When enabled, code inside SVG will not be security-filtered. Be aware of potential XSS risks", "autoLaunchMode0": "Do not launch automatically", "autoLaunchMode1": "Auto launch after booting", "autoLaunchMode2": "Auto launch and minimize the main interface after booting",
app/appearance/langs/es_ES.json+2 −0 modified@@ -128,6 +128,8 @@ "click": "Hacer clic", "allowHTMLBLockScript": "Permitir la ejecución de scripts dentro de bloques HTML", "allowHTMLBLockScriptTip": "Después de habilitarlo, el script en el bloque HTML no se desinfectará. Tenga en cuenta el riesgo potencial de ataques XSS", + "allowSVGScript": "Permitir ejecutar scripts dentro del SVG", + "allowSVGScriptTip": "Al activarlo, el código dentro del SVG no será filtrado por seguridad。Tenga en cuenta el riesgo potencial de XSS", "autoLaunchMode0": "No iniciar automáticamente", "autoLaunchMode1": "Inicio automático después del arranque", "autoLaunchMode2": "Iniciar automáticamente y minimizar la interfaz principal después del arranque",
app/appearance/langs/fr_FR.json+2 −0 modified@@ -128,6 +128,8 @@ "click": "Cliquez sur", "allowHTMLBLockScript": "Autoriser l'exécution de scripts dans les blocs HTML", "allowHTMLBLockScriptTip": "Après activation, le script dans le bloc HTML ne sera pas nettoyé. Veuillez être conscient du risque potentiel d'attaques XSS", + "allowSVGScript": "Autoriser l'exécution de scripts dans le SVG", + "allowSVGScriptTip": "Si activé, le code dans le SVG ne sera pas filtré pour la sécurité。Veuillez noter le risque potentiel de XSS", "autoLaunchMode0": "Ne pas lancer automatiquement", "autoLaunchMode1": "Lancement automatique après le démarrage", "autoLaunchMode2": "Lancement automatique et minimisation de l'interface principale après le démarrage",
app/appearance/langs/he_IL.json+2 −0 modified@@ -128,6 +128,8 @@ "click": "לחץ", "allowHTMLBLockScript": "אפשר הפעלת סקריפטים בתוך בלוקים של HTML", "allowHTMLBLockScriptTip": "לאחר הפעלה, הסקריפט בבלוק HTML לא יחוטא, שים לב לסיכון פוטנציאלי של התקפות XSS", + "allowSVGScript": "לאפשר הרצת סקריפטים בתוך SVG", + "allowSVGScriptTip": "בהפעלת האפשרות, הקוד בתוך SVG לא יסונן מבחינה בטיחותית — יש לשים לב לסיכון אפשרי של XSS", "autoLaunchMode0": "אל תצא אוטומטית", "autoLaunchMode1": "צא אוטומטית לאחר אתחול", "autoLaunchMode2": "צא אוטומטית ומזער את הממשק העיקרי לאחר אתחול",
app/appearance/langs/it_IT.json+2 −0 modified@@ -128,6 +128,8 @@ "click": "Clicca", "allowHTMLBLockScript": "Consenti l'esecuzione di script nei blocchi HTML", "allowHTMLBLockScriptTip": "Dopo l'abilitazione, lo script nel blocco HTML non verrà sanificato. Si prega di essere consapevoli del potenziale rischio di attacchi XSS.", + "allowSVGScript": "Consenti l'esecuzione di script all'interno di SVG", + "allowSVGScriptTip": "Se abilitato, il codice all'interno dell'SVG non sarà filtrato per motivi di sicurezza. Prestare attenzione al rischio potenziale di XSS", "autoLaunchMode0": "Non avviare automaticamente", "autoLaunchMode1": "Avvio automatico dopo l'accensione", "autoLaunchMode2": "Avvio automatico e minimizzazione dell'interfaccia principale dopo l'accensione",
app/appearance/langs/ja_JP.json+2 −0 modified@@ -128,6 +128,8 @@ "click": "クリック", "allowHTMLBLockScript": "HTML ブロック内のスクリプトの実行を許可", "allowHTMLBLockScriptTip": "HTML ブロック内のスクリプトはサニタイズされません。XSS 攻撃の潜在的なリスクに十分注意してください", + "allowSVGScript": "SVG 内のスクリプトを実行許可", + "allowSVGScriptTip": "有効にすると SVG 内のコードはセキュリティフィルタの対象になりません。潜在的な XSS 攻撃に注意してください", "autoLaunchMode0": "自動的に起動しない", "autoLaunchMode1": "システムの起動後に自動的に起動する", "autoLaunchMode2": "システムの起動後にインターフェースを最小化して自動的に起動する",
app/appearance/langs/ko_KR.json+2 −0 modified@@ -128,6 +128,8 @@ "click": "클릭", "allowHTMLBLockScript": "HTML 블록 내 스크립트 실행 허용", "allowHTMLBLockScriptTip": "활성화하면 HTML 블록의 스크립트가 삭제되지 않습니다. XSS 공격의 잠재적 위험에 주의하세요", + "allowSVGScript": "SVG 내부 스크립트 실행 허용", + "allowSVGScriptTip": "활성화하면 SVG 내의 코드는 보안 필터링 대상이 되지 않습니다。잠재적 XSS 공격에 주의하세요", "autoLaunchMode0": "자동으로 시작하지 않음", "autoLaunchMode1": "부팅 후 자동 시작", "autoLaunchMode2": "부팅 후 자동 시작 및 메인 인터페이스 최소화",
app/appearance/langs/pl_PL.json+2 −0 modified@@ -128,6 +128,8 @@ "click": "Kliknij", "allowHTMLBLockScript": "Zezwalaj na wykonywanie skryptów w blokach HTML", "allowHTMLBLockScriptTip": "Po włączeniu skrypt w bloku HTML nie będzie czyszczony, proszę być świadomym potencjalnego ryzyka ataków XSS", + "allowSVGScript": "Zezwól na wykonywanie skryptów w SVG", + "allowSVGScriptTip": "Po włączeniu kod w SVG nie będzie filtrowany pod kątem bezpieczeństwa — uważaj na potencjalne ataki XSS", "autoLaunchMode0": "Nie uruchamiaj automatycznie", "autoLaunchMode1": "Uruchom automatycznie po włączeniu", "autoLaunchMode2": "Uruchom automatycznie i zminimalizuj główny interfejs po włączeniu",
app/appearance/langs/pt_BR.json+2 −0 modified@@ -128,6 +128,8 @@ "click": "Clique", "allowHTMLBLockScript": "Permitir execução de scripts dentro de blocos HTML", "allowHTMLBLockScriptTip": "Quando ativado, o script no bloco HTML não será sanitizado, esteja ciente do risco potencial de ataques XSS", + "allowSVGScript": "Permitir execução de scripts dentro de SVG", + "allowSVGScriptTip": "Ao ativar, o código dentro do SVG não será filtrado por segurança。Atenção ao risco potencial de XSS", "autoLaunchMode0": "Não iniciar automaticamente", "autoLaunchMode1": "Iniciar automaticamente após inicialização", "autoLaunchMode2": "Iniciar automaticamente e minimizar a interface principal após inicialização",
app/appearance/langs/ru_RU.json+2 −0 modified@@ -128,6 +128,8 @@ "click": "Клик", "allowHTMLBLockScript": "Разрешить выполнение скриптов внутри HTML блоков", "allowHTMLBLockScriptTip": "После включения скрипт в HTML блоке не будет очищен, Пожалуйста, имейте в виду потенциальный риск XSS-атак", + "allowSVGScript": "Разрешить выполнение скриптов в SVG", + "allowSVGScriptTip": "При включении код внутри SVG не будет проходить фильтрацию безопасности — будьте внимательны к потенциальным XSS-уязвимостям", "autoLaunchMode0": "Не запускать автоматически", "autoLaunchMode1": "Автозапуск после загрузки", "autoLaunchMode2": "Автозапуск и минимизация главного интерфейса после загрузки",
app/appearance/langs/tr_TR.json+2 −0 modified@@ -128,6 +128,8 @@ "click": "Tıkla", "allowHTMLBLockScript": "HTML bloklarındaki betiklerin çalıştırılmasına izin ver", "allowHTMLBLockScriptTip": "Etkinleştirildiğinde, HTML bloğundaki betikler filtrelenmez. XSS saldırısı riski olduğunu unutma", + "allowSVGScript": "SVG içindeki betiklerin çalıştırılmasına izin ver", + "allowSVGScriptTip": "Etkinleştirildiğinde SVG içindeki kod güvenlik filtresinden geçirilmez, potansiyel XSS saldırılarına karşı dikkatli olun", "autoLaunchMode0": "Otomatik başlatma", "autoLaunchMode1": "Açılışta otomatik başlat", "autoLaunchMode2": "Açılışta otomatik başlat ve ana arayüzü küçült",
app/appearance/langs/zh_CHT.json+2 −0 modified@@ -128,6 +128,8 @@ "click": "點擊", "allowHTMLBLockScript": "允許執行 HTML 塊內腳本", "allowHTMLBLockScriptTip": "啟用後將不對 HTML 塊中的程式碼進行安全過濾,請注意潛在的 XSS 攻擊風險", + "allowSVGScript": "允許執行 SVG 內腳本", + "allowSVGScriptTip": "啟用後將不對 SVG 中的程式碼進行安全過濾,請注意潛在的 XSS 攻擊風險", "autoLaunchMode0": "不自動啟動", "autoLaunchMode1": "開機自動啟動", "autoLaunchMode2": "開機後自動啟動並最小化主介面",
app/appearance/langs/zh_CN.json+2 −0 modified@@ -128,6 +128,8 @@ "click": "点击", "allowHTMLBLockScript": "允许执行 HTML 块内脚本", "allowHTMLBLockScriptTip": "启用后将不对 HTML 块中的代码进行安全过滤,请注意潜在的 XSS 攻击风险", + "allowSVGScript": "允许执行 SVG 内脚本", + "allowSVGScriptTip": "启用后将不对 SVG 中的代码进行安全过滤,请注意潜在的 XSS 攻击风险", "autoLaunchMode0": "不自动启动", "autoLaunchMode1": "开机后自动启动", "autoLaunchMode2": "开机后自动启动并最小化主界面",
app/src/config/editor.ts+9 −0 modified@@ -305,6 +305,14 @@ export const editor = { <textarea class="b3-text-field fn__block" id="katexMacros" spellcheck="false">${window.siyuan.config.editor.katexMacros}</textarea> </div> </div> +<label class="fn__flex b3-label"> + <div class="fn__flex-1"> + ${window.siyuan.languages.allowSVGScript} + <div class="b3-label__text">${window.siyuan.languages.allowSVGScriptTip}</div> + </div> + <span class="fn__space"></span> + <input class="b3-switch fn__flex-center" id="allowSVGScript" type="checkbox"${window.siyuan.config.editor.allowSVGScript ? " checked" : ""}/> +</label> <label class="fn__flex b3-label"> <div class="fn__flex-1"> ${window.siyuan.languages.allowHTMLBLockScript} @@ -470,6 +478,7 @@ export const editor = { inlineStrikethrough: (editor.element.querySelector("#editorMarkdownInlineStrikethrough") as HTMLInputElement).checked, inlineMark: (editor.element.querySelector("#editorMarkdownInlineMark") as HTMLInputElement).checked }, + allowSVGScript: (editor.element.querySelector("#allowSVGScript") as HTMLInputElement).checked, allowHTMLBLockScript: (editor.element.querySelector("#allowHTMLBLockScript") as HTMLInputElement).checked, justify: (editor.element.querySelector("#justify") as HTMLInputElement).checked, rtl: (editor.element.querySelector("#rtl") as HTMLInputElement).checked,
app/src/config/search.ts+1 −1 modified@@ -28,7 +28,7 @@ export const initConfigSearch = (element: HTMLElement, app: App) => { "editorMarkdownInlineTag", "editorMarkdownInlineTagTip", "editorMarkdownInlineMath", "editorMarkdownInlineMathTip", "editorMarkdownInlineStrikethrough", "editorMarkdownInlineStrikethroughTip", "editorMarkdownInlineMark", "editorMarkdownInlineMarkTip", "allowHTMLBLockScript", "allowHTMLBLockScriptTip", "backlinkExpandTip", "backmentionExpandTip", - "backlinkContainChildren", "backlinkContainChildrenTip" + "backlinkContainChildren", "backlinkContainChildrenTip", "allowSVGScript", "allowSVGScriptTip" ]), // 文档树
app/src/mobile/settings/editor.ts+9 −0 modified@@ -24,6 +24,7 @@ const setEditor = (modelMainElement: Element) => { inlineStrikethrough: (modelMainElement.querySelector("#editorMarkdownInlineStrikethrough") as HTMLInputElement).checked, inlineMark: (modelMainElement.querySelector("#editorMarkdownInlineMark") as HTMLInputElement).checked }; + window.siyuan.config.editor.allowSVGScript = (modelMainElement.querySelector("#allowSVGScript") as HTMLInputElement).checked; window.siyuan.config.editor.allowHTMLBLockScript = (modelMainElement.querySelector("#allowHTMLBLockScript") as HTMLInputElement).checked; window.siyuan.config.editor.dynamicLoadBlocks = dynamicLoadBlocks; window.siyuan.config.editor.justify = (modelMainElement.querySelector("#justify") as HTMLInputElement).checked; @@ -279,6 +280,14 @@ export const initEditor = () => { <textarea class="b3-text-field fn__block" id="katexMacros">${window.siyuan.config.editor.katexMacros}</textarea> <div class="b3-label__text">${window.siyuan.languages.katexMacrosTip}</div> </div> +<label class="fn__flex b3-label"> + <div class="fn__flex-1"> + ${window.siyuan.languages.allowSVGScript} + <div class="b3-label__text">${window.siyuan.languages.allowSVGScriptTip}</div> + </div> + <span class="fn__space"></span> + <input class="b3-switch fn__flex-center" id="allowSVGScript" type="checkbox"${window.siyuan.config.editor.allowSVGScript ? " checked" : ""}/> +</label> <label class="fn__flex b3-label"> <div class="fn__flex-1"> ${window.siyuan.languages.allowHTMLBLockScript}
app/src/protyle/export/index.ts+1 −0 modified@@ -509,6 +509,7 @@ ${getIconScript(servePath)} config: { appearance: { mode: 0, codeBlockThemeDark: "${window.siyuan.config.appearance.codeBlockThemeDark}", codeBlockThemeLight: "${window.siyuan.config.appearance.codeBlockThemeLight}" }, editor: { + allowSVGScriptTip: ${window.siyuan.config.editor.allowSVGScript}, allowHTMLBLockScript: ${window.siyuan.config.editor.allowHTMLBLockScript}, fontSize: ${window.siyuan.config.editor.fontSize}, codeLineWrap: true,
app/src/types/config.d.ts+5 −0 modified@@ -350,6 +350,11 @@ declare namespace Config { */ export interface IEditor { + /** + * Whether to allow to execute javascript in the SVG + */ + allowSVGScript: boolean; + /** * Whether to allow to execute javascript in the HTML block */
kernel/conf/editor.go+1 −0 modified@@ -19,6 +19,7 @@ package conf import "github.com/siyuan-note/siyuan/kernel/util" type Editor struct { + AllowSVGScript bool `json:"allowSVGScript"` // 允许执行 SVG 内脚本 AllowHTMLBLockScript bool `json:"allowHTMLBLockScript"` // 允许执行 HTML 块内脚本 FontSize int `json:"fontSize"` // 字体大小 FontSizeScrollZoom bool `json:"fontSizeScrollZoom"` // 字体大小是否支持滚轮缩放
kernel/server/serve.go+19 −2 modified@@ -545,8 +545,7 @@ func serveAssets(ginServer *gin.Engine) { } } - if serveThumbnail(context, p, requestPath) { - // 如果请求缩略图服务成功则返回 + if serveThumbnail(context, p, requestPath) || serveSVG(context, p) { return } @@ -562,6 +561,24 @@ func serveAssets(ginServer *gin.Engine) { }) } +func serveSVG(context *gin.Context, assetAbsPath string) bool { + if strings.HasSuffix(assetAbsPath, ".svg") { + data, err := os.ReadFile(assetAbsPath) + if err != nil { + logging.LogErrorf("read svg file failed: %s", err) + return false + } + + if !model.Conf.Editor.AllowSVGScript { + data = []byte(util.RemoveScriptsInSVG(string(data))) + } + + context.Data(200, "image/svg+xml", data) + return true + } + return false +} + func serveThumbnail(context *gin.Context, assetAbsPath, requestPath string) bool { if style := context.Query("style"); style == "thumb" && model.NeedGenerateAssetsThumbnail(assetAbsPath) { // 请求缩略图 thumbnailPath := filepath.Join(util.TempDir, "thumbnails", "assets", requestPath)
kernel/util/misc.go+50 −0 modified@@ -27,6 +27,7 @@ import ( "unicode" "github.com/88250/lute/html" + "github.com/siyuan-note/logging" ) func init() { @@ -220,3 +221,52 @@ func ReplaceStr(strs []string, old, new string) (ret []string, changed bool) { ret = strs return } + +// RemoveScriptsInSVG 移除 SVG 中的 <script> 标签及其内部所有内容 +func RemoveScriptsInSVG(svgInput string) string { + // 1. 将字符串解析为节点树 + doc, err := html.Parse(strings.NewReader(svgInput)) + if err != nil { + logging.LogWarnf("parse svg failed: %v", err) + return svgInput + } + + // 2. 定义递归移除逻辑 + var walk func(*html.Node) + walk = func(n *html.Node) { + // 倒序遍历子节点,确保删除操作不影响后续迭代 + for c := n.FirstChild; c != nil; { + next := c.NextSibling + // 检查标签名是否为 script + if c.Type == html.ElementNode && strings.EqualFold(c.Data, "script") { + n.RemoveChild(c) + } else { + // 递归处理子节点 + walk(c) + } + c = next + } + } + + // 3. 执行移除 + walk(doc) + + // 4. 将处理后的树重新渲染回字符串 + var buf bytes.Buffer + if err = html.Render(&buf, doc); err != nil { + logging.LogWarnf("render svg failed: %v", err) + return svgInput + } + + // 5. 提取 SVG 部分 (html.Render 会自动加上 <html><body> 标签) + return extractSVG(buf.String()) +} + +func extractSVG(fullHTML string) string { + start := strings.Index(fullHTML, "<svg") + end := strings.LastIndex(fullHTML, "</svg>") + if start == -1 || end == -1 { + return fullHTML + } + return fullHTML[start : end+6] +}
Vulnerability mechanics
Synthesis attempt was rejected by the grounding validator. Re-run pending.
References
5- github.com/advisories/GHSA-pcjq-j3mq-jv5jghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-23645ghsaADVISORY
- github.com/siyuan-note/siyuan/commit/11115da3d0de950593ee4ce375cf7f9018484388ghsax_refsource_MISCWEB
- github.com/siyuan-note/siyuan/issues/16844ghsax_refsource_MISCWEB
- github.com/siyuan-note/siyuan/security/advisories/GHSA-pcjq-j3mq-jv5jghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.