A AegiFlow
MEDIUMCVSS 4.9

GHSA-3cm4-ccvw-6xr6

SiYuan: /history/*path and /repo/diff/*path potentially exposing historical snapshots of data/.siyuan/publishAccess.json and data/templates/*

Published
2026-10-05
Modified
2026-10-05
Sources
github-advisory

Summary

### Summary `GHSA-c8r8-95hg-mp34` added a centralized guard, `util.IsForbiddenAbsPath()`, specifically to block access to a small set of sensitive files: `conf/conf.json` (plaintext `accessAuthCode`/API token/cookie key), `data/snippets/conf.json`, the entire `data/templates/` directory, and `data/.siyuan/publishAccess.json` (plaintext publish-mode passwords). It was applied to `kernel/api/file.go` and `kernel/mcp/tools/file.go`. Two other routes in the same server that serve arbitrary files by path, `/history/*path` and `/repo/diff/*path`, construct their target paths independently and were not updated to call this new guard. Since the repo/history snapshot system's tracked root is `data/` (confirmed by `getSyncIgnoreLines()`, whose ignore file lives at `data/.siyuan/syncignore` with entries relative to `data/`), both `data/.siyuan/publishAccess.json` and `data/templates/*` fall within the scope that can legitimately be captured in historical snapshots, meaning a prior version of either file can exist in `util.HistoryDir`/the repo-diff temp checkout even after the live file has been protected by the new guard. This is CWE-862 (Missing Authorization) applied to a very recently introduced protection mechanism. ### Details `kernel/server/serve.go`, `/history/*path` (around line 994): ```go ginServer.GET("/history/*path", model.CheckAuth, model.CheckAdminRole, func(context *gin.Context) { p := filepath.Join(util.HistoryDir, context.Param("path")) // 加密笔记本的历史是密文(.sy/assets/AV),需先解密再输出 if serveEncryptedHistory(context, p) { return } secureAssetContentHeaders(context, p, p) http.ServeFile(context.Writer, context.Request, p) }) ``` No call to `util.IsForbiddenAbsPath(p)` anywhere in this handler. `kernel/server/serve.go`, `/repo/diff/*path` (around line 1241): ```go ginServer.GET("/repo/diff/*path", model.CheckAuth, model.CheckAdminRole, func(context *gin.Context) { requestPath := filepath.Clean(context.Param("path")) if strings.Contains(requestPath, "..") { context.Status(http.StatusUnauthorized) return } ... p := filepath.Join(repoDiffBaseDir, requestPath) if !gulu.File.IsSubPath(repoDiffBaseDir, p) { context.Status(http.StatusUnauthorized) return } http.ServeFile(context.Writer, context.Request, p) }) ``` This route does have its own traversal protection (`..` rejection and `IsSubPath` containment within `repoDiffBaseDir`), but that only prevents escaping the diff-checkout directory, it does nothing to prevent retrieving a *legitimately checked-out historical copy* of `publishAccess.json` or a templates file from within that directory, which is exactly what the new guard exists to prevent regardless of which directory the copy currently sits in. `util.IsForbiddenAbsPath()` itself (`kernel/util/path_guard.go`, introduced by the referenced fix) confirms the intended scope: ```go // 禁止访问 data/.siyuan/publishAccess.json(含发布模式明文访问密码) publishAccessPath := NormalizeAndResolve(filepath.Join(DataDir, ".siyuan", "publishAccess.json")) if fileNorm == publishAccessPath { return true } ``` and ```go // 禁止访问 data/templates 目录(含目录本身及其全部子路径) templatesBase := NormalizeAndResolve(filepath.Join(DataDir, "templates")) if fileNorm == templatesBase || gulu.File.IsSubPath(templatesBase, fileNorm) { return true } ``` Both are paths within `data/`, the same root the sync/history/repo system tracks. ### Step-by-step reproduction 1. As the workspace admin, enable Publish with a password on at least one notebook (creating `data/.siyuan/publishAccess.json` with a plaintext password), then let a sync/backup snapshot capture this state (or check whether local history capture already covers `data/.siyuan/` in the deployed version). 2. Change or remove the publish password, so the live `publishAccess.json` no longer contains the old plaintext password the new guard is meant to hide, going forward. 3. As the admin, request the historical/di

Affected packages

EcosystemPackageAffected versionsFixed versions
Gogithub.com/siyuan-note/siyuan/kernel—0.0.0-20260816034002-035bf9a8c311

Remediation: Upgrade to 0.0.0-20260816034002-035bf9a8c311 or later.

References

Includes data from the GitHub Advisory Database, licensed under CC-BY 4.0.