{"schema":"threatline.record/2","record":{"id":"GHSA-3cm4-ccvw-6xr6","aliases":["CVE-2026-82651","GHSA-3cm4-ccvw-6xr6","GO-2026-6662"],"summary":"SiYuan: /history/*path and /repo/diff/*path potentially exposing historical snapshots of data/.siyuan/publishAccess.json and data/templates/*","details":"### Summary\n`GHSA-c8r8-95hg-mp34` added a centralized guard,\n`util.IsForbiddenAbsPath()`, specifically to block access to a small\nset of sensitive files: `conf/conf.json` (plaintext\n`accessAuthCode`/API token/cookie key), `data/snippets/conf.json`,\nthe entire `data/templates/` directory, and\n`data/.siyuan/publishAccess.json` (plaintext publish-mode passwords).\nIt was applied to `kernel/api/file.go` and `kernel/mcp/tools/file.go`.\nTwo other routes in the same server that serve arbitrary files by path,\n`/history/*path` and `/repo/diff/*path`, construct their target paths\nindependently and were not updated to call this new guard. Since the\nrepo/history snapshot system's tracked root is `data/` (confirmed by\n`getSyncIgnoreLines()`, whose ignore file lives at\n`data/.siyuan/syncignore` with entries relative to `data/`), both\n`data/.siyuan/publishAccess.json` and `data/templates/*` fall within\nthe scope that can legitimately be captured in historical snapshots,\nmeaning a prior version of either file can exist in\n`util.HistoryDir`/the repo-diff temp checkout even after the live file\nhas been protected by the new guard. This is CWE-862 (Missing\nAuthorization) applied to a very recently introduced protection\nmechanism.\n\n### Details\n`kernel/server/serve.go`, `/history/*path` (around line 994):\n```go\nginServer.GET(\"/history/*path\", model.CheckAuth, model.CheckAdminRole, func(context *gin.Context) {\n    p := filepath.Join(util.HistoryDir, context.Param(\"path\"))\n    // 加密笔记本的历史是密文（.sy/assets/AV），需先解密再输出\n    if serveEncryptedHistory(context, p) {\n        return\n    }\n    secureAssetContentHeaders(context, p, p)\n    http.ServeFile(context.Writer, context.Request, p)\n})\n```\nNo call to `util.IsForbiddenAbsPath(p)` anywhere in this handler.\n\n`kernel/server/serve.go`, `/repo/diff/*path` (around line 1241):\n```go\nginServer.GET(\"/repo/diff/*path\", model.CheckAuth, model.CheckAdminRole, func(context *gin.Context) {\n    requestPath := filepath.Clean(context.Param(\"path\"))\n    if strings.Contains(requestPath, \"..\") {\n        context.Status(http.StatusUnauthorized)\n        return\n    }\n    ...\n    p := filepath.Join(repoDiffBaseDir, requestPath)\n    if !gulu.File.IsSubPath(repoDiffBaseDir, p) {\n        context.Status(http.StatusUnauthorized)\n        return\n    }\n    http.ServeFile(context.Writer, context.Request, p)\n})\n```\nThis route does have its own traversal protection (`..` rejection and\n`IsSubPath` containment within `repoDiffBaseDir`), but that only\nprevents escaping the diff-checkout directory, it does nothing to\nprevent retrieving a *legitimately checked-out historical copy* of\n`publishAccess.json` or a templates file from within that directory,\nwhich is exactly what the new guard exists to prevent regardless of\nwhich directory the copy currently sits in.\n\n`util.IsForbiddenAbsPath()` itself (`kernel/util/path_guard.go`,\nintroduced by the referenced fix) confirms the intended scope:\n```go\n// 禁止访问 data/.siyuan/publishAccess.json（含发布模式明文访问密码）\npublishAccessPath := NormalizeAndResolve(filepath.Join(DataDir, \".siyuan\", \"publishAccess.json\"))\nif fileNorm == publishAccessPath {\n    return true\n}\n```\nand\n```go\n// 禁止访问 data/templates 目录（含目录本身及其全部子路径）\ntemplatesBase := NormalizeAndResolve(filepath.Join(DataDir, \"templates\"))\nif fileNorm == templatesBase || gulu.File.IsSubPath(templatesBase, fileNorm) {\n    return true\n}\n```\nBoth are paths within `data/`, the same root the sync/history/repo\nsystem tracks.\n\n### Step-by-step reproduction\n1. As the workspace admin, enable Publish with a password on at least\n   one notebook (creating `data/.siyuan/publishAccess.json` with a\n   plaintext password), then let a sync/backup snapshot capture this\n   state (or check whether local history capture already covers\n   `data/.siyuan/` in the deployed version).\n2. Change or remove the publish password, so the live\n   `publishAccess.json` no longer contains the old plaintext password\n   the new guard is meant to hide, going forward.\n3. As the admin, request the historical/diff version instead of the\n   live file:\n   ```bash\n   curl -s http://<target>:6806/history/<snapshot-path-to-publishAccess.json> \\\n     -u \"<workspaceName>:<accessAuthCode>\"\n   curl -s http://<target>:6806/repo/diff/<diff-path-to-publishAccess.json> \\\n     -u \"<workspaceName>:<accessAuthCode>\"\n   ```\n4. Expected if consistently protected, matching the behavior the new\n   guard already provides on the live-file endpoints: rejected.\n   Observed: neither handler calls `IsForbiddenAbsPath`, so the\n   historical copy is served if it exists in that location.\n\n*(Not run against a live compiled kernel, same sandbox limitation noted\nthroughout this review; both handlers are read directly from source at\nthe reviewed commit, and `IsForbiddenAbsPath`'s scope, plus the\nsync-root confirmation via `getSyncIgnoreLines()`, are quoted directly\nabove. Whether these specific files are captured by history/repo\nsnapshots in a given deployment depends on the workspace's actual\nusage history and was not independently verified against a live\ninstance in this review.)*\n\n### Impact\nAn admin-authenticated request to either route can potentially retrieve\na historical copy of `data/.siyuan/publishAccess.json` (disclosing a\nplaintext publish-mode password even after it has been changed or the\nlive file has been protected) or a `data/templates/*` file, directly\nundermining the protection `GHSA-c8r8-95hg-mp34` was written four days\nprior to this review specifically to provide, via two routes that\npredate that fix and were not updated alongside it.\n```\n\n## Affected products\n\n| Field | Value |\n|---|---|\n| Ecosystem | **Go** |\n| Package name | `github.com/siyuan-note/siyuan/kernel` |\n| Affected versions | Present as of commit `251596f` (2026-08-12, the version this review confirmed), i.e. postdates and was not covered by the `GHSA-c8r8-95hg-mp34` fix (commit `3542530`, 2026-08-08) |\n| Patched versions | *(none yet, leave blank until a fix is released)* |\n\n## Severity\n\n| Field | Value |\n|---|---|\n| Vector string | `CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:M/I:N/A:N` |\n| Score | **~5.9 (Medium)**, `PR:H` since admin authentication is required at the HTTP layer, confidentiality impact scoped to whatever sensitive historical content happens to exist in the tracked snapshots for a given deployment (a real but deployment-dependent condition, honestly reflected as Medium rather than assumed to always be present), no integrity/availability impact since both are read-only. |\n\n## Weaknesses (CWE)\n\n- **CWE-862**: Missing Authorization (primary)\n- **CWE-200**: Exposure of Sensitive Information to an Unauthorized Actor\n\n## Notes for filing\n- Direct, narrow follow-up to `GHSA-c8r8-95hg-mp34`; recommend\n  referencing that advisory directly when filing, since this is\n  precisely the \"sibling caller missed\" pattern that fix's own\n  centralization (moving the check into a shared `util` function) was\n  presumably intended to prevent, just for two callers that existed\n  before the shared function did and weren't migrated to it.\n- Suggested fix: add `if util.IsForbiddenAbsPath(p) { ... reject ... }`\n  to both handlers, matching the pattern already applied in\n  `kernel/api/file.go` and `kernel/mcp/tools/file.go`.","categories":["Libraries & packages"],"product":"github.com/siyuan-note/siyuan/kernel","vendor":"","ecosystem":"go","affected_ranges":[{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-3cm4-ccvw-6xr6","source_modified_at":"2026-10-05T17:32:45Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"go","name":"github.com/siyuan-note/siyuan/kernel"},"range":"< 0.0.0-20260816034002-035bf9a8c311"},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-3cm4-ccvw-6xr6","source_modified_at":"2026-10-07T17:55:31.429031793Z","fetched_at":"2026-10-23T18:45:31.882637Z","package":{"name":"github.com/siyuan-note/siyuan/kernel","ecosystem":"Go","purl":"pkg:golang/github.com/siyuan-note/siyuan/kernel"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"0.0.0-20260816034002-035bf9a8c311"}]}],"versions":[]}],"fixed_versions":[{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-3cm4-ccvw-6xr6","source_modified_at":"2026-10-05T17:32:45Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"go","name":"github.com/siyuan-note/siyuan/kernel"},"version":"0.0.0-20260816034002-035bf9a8c311"},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-3cm4-ccvw-6xr6","source_modified_at":"2026-10-07T17:55:31.429031793Z","fetched_at":"2026-10-23T18:45:31.882637Z","package":{"name":"github.com/siyuan-note/siyuan/kernel","ecosystem":"Go","purl":"pkg:golang/github.com/siyuan-note/siyuan/kernel"},"version":"0.0.0-20260816034002-035bf9a8c311"}],"cvss":4.9,"severity":"Medium","kev":{"listed":false},"epss":{"probability":null,"percentile":null,"score_date":null},"published_at":"2026-10-05T17:32:44Z","modified_at":"2026-10-07T17:55:31.429031793Z","provenance":[{"source":"GitHub Advisory Database","url":"https://github.com/advisories/GHSA-3cm4-ccvw-6xr6","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-05T17:32:45Z"},{"source":"OSV","url":"https://osv.dev/vulnerability/GHSA-3cm4-ccvw-6xr6","fetched_at":"2026-10-23T18:45:31.882637Z","source_modified_at":"2026-10-07T17:55:31.429031793Z"}],"sources":["GitHub Advisory Database","OSV"],"references":[{"url":"https://github.com/siyuan-note/siyuan/security/advisories/GHSA-3cm4-ccvw-6xr6","source":"GitHub Advisory Database"},{"url":"https://github.com/siyuan-note/siyuan/commit/035bf9a8c311c6f8468f2f1054b4d5e1711787e2","source":"GitHub Advisory Database"},{"url":"https://github.com/siyuan-note/siyuan/releases/tag/v3.8.1","source":"GitHub Advisory Database"},{"url":"https://github.com/advisories/GHSA-3cm4-ccvw-6xr6","source":"GitHub Advisory Database"}],"locations":[],"motoko_assessment":{"status":"requires_architecture_review","automatic_remediation":false},"classification":{"method":"product-first software-layer rules","rules_version":"2.1","matched_on":"ecosystem","confidence":"heuristic","reviewed":false},"change_timestamp":"2026-10-07T17:55:31.429031793Z","content_hash":"d0ddc96765c215c1962fe80431936587bb6ade4dda2fc8274942c2dfe3a31c12"}}
