VYPR
High severityOSV Advisory· Published Jan 19, 2026· Updated Jan 20, 2026

SiYuan Vulnerable to Arbitrary File Read via File Copy Functionality

CVE-2026-23851

Description

SiYuan is a personal knowledge management system. Versions prior to 3.5.4 contain a logic vulnerability in the /api/file/globalCopyFiles endpoint. The function allows authenticated users to copy files from any location on the server's filesystem into the application's workspace without proper path validation. The vulnerability exists in the api/file.go source code. The function globalCopyFiles accepts a list of source paths (srcs) from the JSON request body. While the code checks if the source file exists using filelock.IsExist(src), it fails to validate whether the source path resides within the authorized workspace directory. Version 3.5.4 patches the issue.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/siyuan-note/siyuan/kernelGo
< 0.0.0-20260118092521-f8f4b517077b0.0.0-20260118092521-f8f4b517077b

Affected products

1

Patches

2
f8f4b517077b

:lock: Arbitrary file reading vulnerability https://github.com/siyuan-note/siyuan/issues/16860

https://github.com/siyuan-note/siyuanDanielJan 18, 2026via ghsa
1 file changed · +1 0
  • kernel/util/path.go+1 0 modified
    @@ -416,6 +416,7 @@ func IsSensitivePath(p string) bool {
     		"passwd":          {},
     		"shadow":          {},
     		"pgpass":          {},
    +		"hosts":           {},
     		"credentials":     {}, // 如 aws credentials
     		"config.json":     {}, // docker config.json 可能含 token
     	}
    
b2274baba2e1

:lock: Arbitrary file reading vulnerability https://github.com/siyuan-note/siyuan/issues/16860

https://github.com/siyuan-note/siyuanDanielJan 18, 2026via ghsa
3 files changed · +118 3
  • kernel/api/file.go+14 2 modified
    @@ -66,14 +66,26 @@ func globalCopyFiles(c *gin.Context) {
     		srcs = append(srcs, s.(string))
     	}
     
    -	for _, src := range srcs {
    -		if !filelock.IsExist(src) {
    +	for i, src := range srcs {
    +		absSrc, _ := filepath.Abs(src)
    +
    +		if !filelock.IsExist(absSrc) {
     			msg := fmt.Sprintf("file [%s] does not exist", src)
     			logging.LogErrorf(msg)
     			ret.Code = -1
     			ret.Msg = msg
     			return
     		}
    +
    +		if util.IsSensitivePath(absSrc) {
    +			msg := fmt.Sprintf("refuse to copy sensitive file [%s]", src)
    +			logging.LogErrorf(msg)
    +			ret.Code = -2
    +			ret.Msg = msg
    +			return
    +		}
    +
    +		srcs[i] = absSrc
     	}
     
     	destDir := arg["destDir"].(string) // 相对于工作空间的路径
    
  • kernel/model/assets.go+12 1 modified
    @@ -248,7 +248,18 @@ func netAssets2LocalAssets0(tree *parse.Tree, onlyImg bool, originalURL string,
     					u = u[:strings.Index(u, "?")]
     				}
     
    -				if !gulu.File.IsExist(u) || gulu.File.IsDir(u) {
    +				if !gulu.File.IsExist(u) {
    +					logging.LogErrorf("local file asset [%s] not exist", u)
    +					continue
    +				}
    +
    +				if gulu.File.IsDir(u) {
    +					logging.LogWarnf("ignore converting directory path [%s] to local asset", u)
    +					continue
    +				}
    +
    +				if util.IsSensitivePath(u) {
    +					logging.LogWarnf("ignore converting sensitive path [%s] to local asset", u)
     					continue
     				}
     
    
  • kernel/util/path.go+92 0 modified
    @@ -347,3 +347,95 @@ func IsPartitionRootPath(path string) bool {
     		return cleanPath == "/"
     	}
     }
    +
    +// IsSensitivePath 对传入路径做统一的敏感性检测。
    +func IsSensitivePath(p string) bool {
    +	if p == "" {
    +		return false
    +	}
    +	pp := filepath.Clean(strings.ToLower(p))
    +
    +	// 精确敏感文件
    +	exact := []string{
    +		"/etc/passwd",
    +		"/etc/shadow",
    +		"/etc/gshadow",
    +		"/var/run/secrets/kubernetes.io/serviceaccount/token",
    +	}
    +	for _, e := range exact {
    +		if pp == e {
    +			return true
    +		}
    +	}
    +
    +	// 敏感目录前缀(UNIX 风格)
    +	prefixes := []string{
    +		"/etc/ssh",
    +		"/root",
    +		"/etc/ssl",
    +		"/etc/letsencrypt",
    +		"/var/lib/docker",
    +		"/.gnupg",
    +		"/.ssh",
    +		"/.aws",
    +		"/.kube",
    +		"/.docker",
    +		"/.config/gcloud",
    +	}
    +	for _, pre := range prefixes {
    +		if strings.HasPrefix(pp, pre) {
    +			return true
    +		}
    +	}
    +
    +	// Windows 常见敏感目录(小写比较)
    +	winPrefixes := []string{
    +		`c:\windows\system32`,
    +		`c:\windows\system`,
    +		`c:\users\`,
    +	}
    +	for _, wp := range winPrefixes {
    +		if strings.HasPrefix(pp, strings.ToLower(wp)) {
    +			return true
    +		}
    +	}
    +
    +	// 文件名级别检查
    +	base := filepath.Base(pp)
    +	n := strings.ToLower(base)
    +	sensitiveNames := map[string]struct{}{
    +		".env":            {},
    +		".env.local":      {},
    +		".npmrc":          {},
    +		".netrc":          {},
    +		"id_rsa":          {},
    +		"id_dsa":          {},
    +		"id_ecdsa":        {},
    +		"id_ed25519":      {},
    +		"authorized_keys": {},
    +		"passwd":          {},
    +		"shadow":          {},
    +		"pgpass":          {},
    +		"credentials":     {}, // 如 aws credentials
    +		"config.json":     {}, // docker config.json 可能含 token
    +	}
    +	if _, ok := sensitiveNames[n]; ok {
    +		return true
    +	}
    +	// 支持 .env.* 之类的模式
    +	if n == ".env" || strings.HasPrefix(n, ".env.") {
    +		return true
    +	}
    +
    +	// 扩展名级别检查
    +	ext := strings.ToLower(filepath.Ext(n))
    +	sensitiveExts := []string{
    +		".pem", ".key", ".p12", ".pfx", ".ppk", ".asc", ".gpg",
    +	}
    +	for _, se := range sensitiveExts {
    +		if ext == se {
    +			return true
    +		}
    +	}
    +	return false
    +}
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.