🔒 Do not execute scripts in assets SVG by default to prevent XSS https://github.com/siyuan-note/siyuan/issues/16844

Signed-off-by: Daniel <845765@qq.com>
This commit is contained in:
Daniel 2026-01-16 18:11:55 +08:00
parent 65532aec99
commit 11115da3d0
No known key found for this signature in database
GPG key ID: 86211BA83DF03017
23 changed files with 125 additions and 3 deletions

View file

@ -128,6 +128,8 @@
"click": "نقر",
"allowHTMLBLockScript": "‫السماح بتنفيذ البرامج النصية في كتل HTML",
"allowHTMLBLockScriptTip": "‫بعد التمكين، لن يتم تصحيح البرنامج النصي في كتلة HTML، يرجى إدراك المخاطر المحتملة لهجمات XSS",
"allowSVGScript": "السماح بتشغيل السكربتات داخل SVG",
"allowSVGScriptTip": "عند التفعيل، لن يتم تصفية الكود داخل SVG لأغراض الأمان. يرجى الانتباه إلى مخاطر XSS المحتملة",
"autoLaunchMode0": "عدم التشغيل تلقائيًا",
"autoLaunchMode1": "التشغيل التلقائي بعد الإقلاع",
"autoLaunchMode2": "التشغيل التلقائي وتقليل الواجهة الرئيسية بعد الإقلاع",

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -128,6 +128,8 @@
"click": "לחץ",
"allowHTMLBLockScript": "אפשר הפעלת סקריפטים בתוך בלוקים של HTML",
"allowHTMLBLockScriptTip": "לאחר הפעלה, הסקריפט בבלוק HTML לא יחוטא, שים לב לסיכון פוטנציאלי של התקפות XSS",
"allowSVGScript": "לאפשר הרצת סקריפטים בתוך SVG",
"allowSVGScriptTip": "בהפעלת האפשרות, הקוד בתוך SVG לא יסונן מבחינה בטיחותית — יש לשים לב לסיכון אפשרי של XSS",
"autoLaunchMode0": "אל תצא אוטומטית",
"autoLaunchMode1": "צא אוטומטית לאחר אתחול",
"autoLaunchMode2": "צא אוטומטית ומזער את הממשק העיקרי לאחר אתחול",

View file

@ -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",

View file

@ -128,6 +128,8 @@
"click": "クリック",
"allowHTMLBLockScript": "HTML ブロック内のスクリプトの実行を許可",
"allowHTMLBLockScriptTip": "HTML ブロック内のスクリプトはサニタイズされません。XSS 攻撃の潜在的なリスクに十分注意してください",
"allowSVGScript": "SVG 内のスクリプトを実行許可",
"allowSVGScriptTip": "有効にすると SVG 内のコードはセキュリティフィルタの対象になりません。潜在的な XSS 攻撃に注意してください",
"autoLaunchMode0": "自動的に起動しない",
"autoLaunchMode1": "システムの起動後に自動的に起動する",
"autoLaunchMode2": "システムの起動後にインターフェースを最小化して自動的に起動する",

View file

@ -128,6 +128,8 @@
"click": "클릭",
"allowHTMLBLockScript": "HTML 블록 내 스크립트 실행 허용",
"allowHTMLBLockScriptTip": "활성화하면 HTML 블록의 스크립트가 삭제되지 않습니다. XSS 공격의 잠재적 위험에 주의하세요",
"allowSVGScript": "SVG 내부 스크립트 실행 허용",
"allowSVGScriptTip": "활성화하면 SVG 내의 코드는 보안 필터링 대상이 되지 않습니다。잠재적 XSS 공격에 주의하세요",
"autoLaunchMode0": "자동으로 시작하지 않음",
"autoLaunchMode1": "부팅 후 자동 시작",
"autoLaunchMode2": "부팅 후 자동 시작 및 메인 인터페이스 최소화",

View file

@ -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",

View file

@ -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",

View file

@ -128,6 +128,8 @@
"click": "Клик",
"allowHTMLBLockScript": "Разрешить выполнение скриптов внутри HTML блоков",
"allowHTMLBLockScriptTip": "После включения скрипт в HTML блоке не будет очищен, Пожалуйста, имейте в виду потенциальный риск XSS-атак",
"allowSVGScript": "Разрешить выполнение скриптов в SVG",
"allowSVGScriptTip": "При включении код внутри SVG не будет проходить фильтрацию безопасности — будьте внимательны к потенциальным XSS-уязвимостям",
"autoLaunchMode0": "Не запускать автоматически",
"autoLaunchMode1": "Автозапуск после загрузки",
"autoLaunchMode2": "Автозапуск и минимизация главного интерфейса после загрузки",

View file

@ -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",

View file

@ -128,6 +128,8 @@
"click": "點擊",
"allowHTMLBLockScript": "允許執行 HTML 塊內腳本",
"allowHTMLBLockScriptTip": "啟用後將不對 HTML 塊中的程式碼進行安全過濾,請注意潛在的 XSS 攻擊風險",
"allowSVGScript": "允許執行 SVG 內腳本",
"allowSVGScriptTip": "啟用後將不對 SVG 中的程式碼進行安全過濾,請注意潛在的 XSS 攻擊風險",
"autoLaunchMode0": "不自動啟動",
"autoLaunchMode1": "開機自動啟動",
"autoLaunchMode2": "開機後自動啟動並最小化主介面",

View file

@ -128,6 +128,8 @@
"click": "点击",
"allowHTMLBLockScript": "允许执行 HTML 块内脚本",
"allowHTMLBLockScriptTip": "启用后将不对 HTML 块中的代码进行安全过滤,请注意潜在的 XSS 攻击风险",
"allowSVGScript": "允许执行 SVG 内脚本",
"allowSVGScriptTip": "启用后将不对 SVG 中的代码进行安全过滤,请注意潜在的 XSS 攻击风险",
"autoLaunchMode0": "不自动启动",
"autoLaunchMode1": "开机后自动启动",
"autoLaunchMode2": "开机后自动启动并最小化主界面",

View file

@ -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,

View file

@ -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"
]),
// 文档树

View file

@ -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}

View file

@ -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,

View file

@ -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
*/

View file

@ -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"` // 字体大小是否支持滚轮缩放

View file

@ -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)

View file

@ -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]
}