From be0a86b45f9843ec3a6b7a70acebaf824fcf46ab Mon Sep 17 00:00:00 2001
From: Daniel <845765@qq.com>
Date: Fri, 10 Apr 2026 00:08:07 +0800
Subject: [PATCH 1/2] :lock:
https://github.com/siyuan-note/siyuan/security/advisories/GHSA-w95v-4h65-j455
Signed-off-by: Daniel <845765@qq.com>
---
app/src/protyle/render/mermaidRender.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/src/protyle/render/mermaidRender.ts b/app/src/protyle/render/mermaidRender.ts
index 526dad274..1ff7c81c2 100644
--- a/app/src/protyle/render/mermaidRender.ts
+++ b/app/src/protyle/render/mermaidRender.ts
@@ -98,7 +98,7 @@ const initMermaid = (mermaidElements: Element[]) => {
try {
renderElement.innerHTML = `${Constants.ZWSP}
`;
const mermaidData = await window.mermaid.render(id, Lute.UnEscapeHTMLStr(item.getAttribute("data-content")));
- renderElement.lastElementChild.innerHTML = mermaidData.svg;
+ renderElement.lastElementChild.innerHTML = mermaidData.svg.replace(/(href|src|xlink:href)\s*=\s*["']\\\\/gi, (match, p1) => `${p1}="about:blank"`);;
} catch (e) {
const errorElement = document.querySelector("#" + id);
renderElement.lastElementChild.innerHTML = `${errorElement.outerHTML}${e.message.replace(/\n/, "
")}
`;
From c1539878c879fac759ae0675d5038b1ac37447ee Mon Sep 17 00:00:00 2001
From: Daniel <845765@qq.com>
Date: Fri, 10 Apr 2026 00:38:50 +0800
Subject: [PATCH 2/2] :recycle: Move IsSubPath to gulu
Signed-off-by: Daniel <845765@qq.com>
---
kernel/api/file.go | 2 +-
kernel/api/import.go | 6 +++---
kernel/api/sync.go | 4 ++--
kernel/api/system.go | 2 +-
kernel/api/workspace.go | 2 +-
kernel/go.mod | 2 +-
kernel/go.sum | 4 ++--
kernel/model/assets.go | 4 ++--
kernel/model/publish_access.go | 4 ++--
kernel/model/sync.go | 2 +-
kernel/model/upload.go | 2 +-
kernel/server/serve.go | 2 +-
kernel/util/file.go | 28 ----------------------------
kernel/util/path.go | 4 ++--
14 files changed, 20 insertions(+), 48 deletions(-)
diff --git a/kernel/api/file.go b/kernel/api/file.go
index bfd15fe0d..7fbd0f5e0 100644
--- a/kernel/api/file.go
+++ b/kernel/api/file.go
@@ -366,7 +366,7 @@ func refuseToAccess(c *gin.Context, fileAbsPath string, ret *gulu.Result) bool {
// 禁止访问 data/templates 目录
templatesBase := normalizeAndResolve(filepath.Join(util.DataDir, "templates"))
- if util.IsSubPath(templatesBase, fileNorm) {
+ if gulu.File.IsSubPath(templatesBase, fileNorm) {
ret.Code = http.StatusForbidden
ret.Msg = http.StatusText(http.StatusForbidden)
c.JSON(http.StatusAccepted, ret)
diff --git a/kernel/api/import.go b/kernel/api/import.go
index e590531ac..289a9a3be 100644
--- a/kernel/api/import.go
+++ b/kernel/api/import.go
@@ -63,7 +63,7 @@ func importSY(c *gin.Context) {
}
writePath := filepath.Join(importDir, file.Filename)
- if !util.IsSubPath(importDir, writePath) {
+ if !gulu.File.IsSubPath(importDir, writePath) {
logging.LogErrorf("import path [%s] is not sub path of import dir [%s]", writePath, importDir)
ret.Code = -1
ret.Msg = "import path is not sub path of import dir"
@@ -232,7 +232,7 @@ func importStdMd(c *gin.Context) {
localPath := arg["localPath"].(string)
toPath := arg["toPath"].(string)
- if util.IsSubPath(util.WorkingDir, localPath) {
+ if gulu.File.IsSubPath(util.WorkingDir, localPath) {
msg := fmt.Sprintf("import from local path [%s] failed: local path is sub path of working dir", localPath)
logging.LogErrorf(msg)
ret.Code = -1
@@ -288,7 +288,7 @@ func importZipMd(c *gin.Context) {
}
writePath := filepath.Join(importDir, file.Filename)
- if !util.IsSubPath(importDir, writePath) {
+ if !gulu.File.IsSubPath(importDir, writePath) {
logging.LogErrorf("import path [%s] is not sub path of import dir [%s]", writePath, importDir)
ret.Code = -1
ret.Msg = "import path is not sub path of import dir"
diff --git a/kernel/api/sync.go b/kernel/api/sync.go
index 5e7b5984f..d4281479c 100644
--- a/kernel/api/sync.go
+++ b/kernel/api/sync.go
@@ -81,7 +81,7 @@ func importSyncProviderWebDAV(c *gin.Context) {
}
writePath := filepath.Join(importDir, f.Filename)
- if !util.IsSubPath(importDir, writePath) {
+ if !gulu.File.IsSubPath(importDir, writePath) {
logging.LogErrorf("import path [%s] is not sub path of import dir [%s]", writePath, importDir)
ret.Code = -1
ret.Msg = "import path is not sub path of import dir"
@@ -274,7 +274,7 @@ func importSyncProviderS3(c *gin.Context) {
}
writePath := filepath.Join(importDir, f.Filename)
- if !util.IsSubPath(importDir, writePath) {
+ if !gulu.File.IsSubPath(importDir, writePath) {
logging.LogErrorf("import path [%s] is not sub path of import dir [%s]", writePath, importDir)
ret.Code = -1
ret.Msg = "import path is not sub path of import dir"
diff --git a/kernel/api/system.go b/kernel/api/system.go
index 6b1e2590a..cb0a4b5d0 100644
--- a/kernel/api/system.go
+++ b/kernel/api/system.go
@@ -442,7 +442,7 @@ func importConf(c *gin.Context) {
}
writePath := filepath.Join(importDir, f.Filename)
- if !util.IsSubPath(importDir, writePath) {
+ if !gulu.File.IsSubPath(importDir, writePath) {
logging.LogErrorf("import path [%s] is not sub path of import dir [%s]", writePath, importDir)
ret.Code = -1
ret.Msg = "import path is not sub path of import dir"
diff --git a/kernel/api/workspace.go b/kernel/api/workspace.go
index a8dca59cb..a2fc03636 100644
--- a/kernel/api/workspace.go
+++ b/kernel/api/workspace.go
@@ -289,7 +289,7 @@ func setWorkspaceDir(c *gin.Context) {
// 改进判断工作空间路径实现 https://github.com/siyuan-note/siyuan/issues/7569
installDirLower := strings.ToLower(filepath.Dir(util.WorkingDir))
pathLower := strings.ToLower(path)
- if strings.HasPrefix(pathLower, installDirLower) && (util.IsSubPath(installDirLower, pathLower) || filepath.Clean(installDirLower) == filepath.Clean(pathLower)) {
+ if strings.HasPrefix(pathLower, installDirLower) && (gulu.File.IsSubPath(installDirLower, pathLower) || filepath.Clean(installDirLower) == filepath.Clean(pathLower)) {
ret.Code = -1
ret.Msg = model.Conf.Language(98)
ret.Data = map[string]any{"closeTimeout": 5000}
diff --git a/kernel/go.mod b/kernel/go.mod
index 913ef9858..20f3e114b 100644
--- a/kernel/go.mod
+++ b/kernel/go.mod
@@ -7,7 +7,7 @@ require (
github.com/88250/clipboard v0.1.5
github.com/88250/epub v0.0.0-20230830085737-c19055cd1f48
github.com/88250/go-humanize v0.0.0-20240424102817-4f78fac47ea7
- github.com/88250/gulu v1.2.3-0.20260124101918-98654a7ca98a
+ github.com/88250/gulu v1.2.3-0.20260409163331-8c1dab1828ba
github.com/88250/lute v1.7.7-0.20260408120251-b51434f68f79
github.com/88250/vitess-sqlparser v0.0.0-20210205111146-56a2ded2aba1
github.com/ClarkThan/ahocorasick v0.0.0-20231011042242-30d1ef1347f4
diff --git a/kernel/go.sum b/kernel/go.sum
index d03f827a0..9769f783a 100644
--- a/kernel/go.sum
+++ b/kernel/go.sum
@@ -12,8 +12,8 @@ github.com/88250/go-humanize v0.0.0-20240424102817-4f78fac47ea7 h1:MafIFwSS0x6A4
github.com/88250/go-humanize v0.0.0-20240424102817-4f78fac47ea7/go.mod h1:HrKCCTin3YNDSLBD02K0AOljjV6eNwc3/zyEI+xyV1I=
github.com/88250/go-sqlite3 v1.14.13-0.20231214121541-e7f54c482950 h1:Pa5hMiBceTVVqrYaDlLio2QSKbXMUmAZPbzCwT5eNCw=
github.com/88250/go-sqlite3 v1.14.13-0.20231214121541-e7f54c482950/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
-github.com/88250/gulu v1.2.3-0.20260124101918-98654a7ca98a h1:s86WMolaqommXG1k2vXg9Gf1iXvgtHSdeD0eeJTQVR8=
-github.com/88250/gulu v1.2.3-0.20260124101918-98654a7ca98a/go.mod h1:D+Db16m0N7r9MLZCMcj1a0ZsEGQAxDZkadOn79Gh0vI=
+github.com/88250/gulu v1.2.3-0.20260409163331-8c1dab1828ba h1:F9nZLeLTSPUIyYrQqprqElKFef4xVI7fzjxkLXRb2Bo=
+github.com/88250/gulu v1.2.3-0.20260409163331-8c1dab1828ba/go.mod h1:D+Db16m0N7r9MLZCMcj1a0ZsEGQAxDZkadOn79Gh0vI=
github.com/88250/lute v1.7.7-0.20260408120251-b51434f68f79 h1:HHydQTaIPrXP9g9LcF1T35VoacyJEh5h3N/3oiA0srg=
github.com/88250/lute v1.7.7-0.20260408120251-b51434f68f79/go.mod h1:WYyUw//5yVw9BJnoVjx7rI/3szsISxNZCYGOqTIrV0o=
github.com/88250/pdfcpu v0.3.14-0.20250424122812-f10e8d9d8d46 h1:Bq1JsDfVbHKUxNL/B2JXd8cC/1h6aFjrlXpGycnh0Hk=
diff --git a/kernel/model/assets.go b/kernel/model/assets.go
index 0b5d6aeef..f61266a05 100644
--- a/kernel/model/assets.go
+++ b/kernel/model/assets.go
@@ -561,7 +561,7 @@ func getAssetAbsPath(relativePath string) (absPath string, err error) {
// 在 data 文件夹下搜索,主要是 data/assets 文件夹
p := filepath.Join(util.DataDir, relativePath)
if gulu.File.IsExist(p) {
- if !util.IsSubPath(util.WorkspaceDir, p) {
+ if !gulu.File.IsSubPath(util.WorkspaceDir, p) {
return "", fmt.Errorf("[%s] is not sub path of workspace", p)
}
return p, nil
@@ -594,7 +594,7 @@ func getAssetAbsPath(relativePath string) (absPath string, err error) {
})
if "" != absPath {
- if !util.IsSubPath(util.WorkspaceDir, absPath) {
+ if !gulu.File.IsSubPath(util.WorkspaceDir, absPath) {
return "", fmt.Errorf("[%s] is not sub path of workspace", absPath)
}
return absPath, nil
diff --git a/kernel/model/publish_access.go b/kernel/model/publish_access.go
index 82442c058..af5240738 100644
--- a/kernel/model/publish_access.go
+++ b/kernel/model/publish_access.go
@@ -245,11 +245,11 @@ func CheckPublishAuthCookie(c *gin.Context, ID string, password string) bool {
func CheckAbsPathAccessableByPublishAccess(c *gin.Context, absPath string, publishAccess PublishAccess) bool {
absPath = filepath.Clean(absPath)
- if util.IsSubPath(util.HistoryDir, absPath) {
+ if gulu.File.IsSubPath(util.HistoryDir, absPath) {
return false
}
- if util.IsSubPath(util.DataDir, absPath) {
+ if gulu.File.IsSubPath(util.DataDir, absPath) {
relPath, err := filepath.Rel(util.DataDir, absPath)
if err != nil {
return true
diff --git a/kernel/model/sync.go b/kernel/model/sync.go
index 5ee0321fb..f610ce0f7 100644
--- a/kernel/model/sync.go
+++ b/kernel/model/sync.go
@@ -490,7 +490,7 @@ func SetSyncProviderLocal(local *conf.Local) (err error) {
return
}
- if util.IsSubPath(absPath, util.WorkspaceDir) {
+ if gulu.File.IsSubPath(absPath, util.WorkspaceDir) {
msg := fmt.Sprintf("endpoint [%s] is parent of workspace", local.Endpoint)
logging.LogErrorf(msg)
err = fmt.Errorf(Conf.Language(77), msg)
diff --git a/kernel/model/upload.go b/kernel/model/upload.go
index 662ac0cb5..3c64e8d3d 100644
--- a/kernel/model/upload.go
+++ b/kernel/model/upload.go
@@ -67,7 +67,7 @@ func InsertLocalAssets(id string, assetAbsPaths []string, isUpload bool) (succMa
continue
}
- if util.IsSubPath(assetsDirPath, assetAbsPath) {
+ if gulu.File.IsSubPath(assetsDirPath, assetAbsPath) {
// 已经位于 assets 目录下的资源文件不处理
// Dragging a file from the assets folder into the editor causes the kernel to exit https://github.com/siyuan-note/siyuan/issues/15355
succMap[baseName] = "assets/" + fName
diff --git a/kernel/server/serve.go b/kernel/server/serve.go
index 7a2a5b145..8ac8f7990 100644
--- a/kernel/server/serve.go
+++ b/kernel/server/serve.go
@@ -444,7 +444,7 @@ func serveAppearance(ginServer *gin.Engine) {
}
siyuan.GET("/appearance/*filepath", func(c *gin.Context) {
filePath := filepath.Join(appearancePath, strings.TrimPrefix(c.Request.URL.Path, "/appearance/"))
- if !util.IsSubPath(appearancePath, filePath) {
+ if !gulu.File.IsSubPath(appearancePath, filePath) {
c.Status(http.StatusUnauthorized)
return
}
diff --git a/kernel/util/file.go b/kernel/util/file.go
index a94a12fe9..f8fdfc983 100644
--- a/kernel/util/file.go
+++ b/kernel/util/file.go
@@ -320,34 +320,6 @@ func FilterFileName(name string) string {
return name
}
-func IsSubPath(absPath, toCheckPath string) bool {
- if 1 > len(absPath) || 1 > len(toCheckPath) {
- return false
- }
- if absPath == toCheckPath { // 相同路径时不认为是子路径
- return false
- }
-
- if gulu.OS.IsWindows() {
- if filepath.IsAbs(absPath) && filepath.IsAbs(toCheckPath) {
- if strings.ToLower(absPath)[0] != strings.ToLower(toCheckPath)[0] {
- // 不在一个盘
- return false
- }
- }
- }
-
- up := ".." + string(os.PathSeparator)
- rel, err := filepath.Rel(absPath, toCheckPath)
- if err != nil {
- return false
- }
- if !strings.HasPrefix(rel, up) && rel != ".." {
- return true
- }
- return false
-}
-
func IsCompressibleAssetImage(p string) bool {
lowerName := strings.ToLower(p)
return strings.HasPrefix(lowerName, "assets/") &&
diff --git a/kernel/util/path.go b/kernel/util/path.go
index a2cd08a25..f3db6aed7 100644
--- a/kernel/util/path.go
+++ b/kernel/util/path.go
@@ -359,14 +359,14 @@ func GetAbsPathInWorkspace(relPath string) (string, error) {
return absPath, nil
}
- if IsSubPath(WorkspaceDir, absPath) {
+ if gulu.File.IsSubPath(WorkspaceDir, absPath) {
return absPath, nil
}
return "", os.ErrPermission
}
func IsAbsPathInWorkspace(absPath string) bool {
- return IsSubPath(WorkspaceDir, absPath)
+ return gulu.File.IsSubPath(WorkspaceDir, absPath)
}
// IsWorkspaceDir 判断指定目录是否是工作空间目录。