CVE-2026-54066: SiYuan Unauthenticated Path Traversal via Double URL Encoding in /assets/ (Publish Mode)
SiYuan's publish-mode file server double-decodes percent-encoded path segments, letting anyone on the network read sensitive workspace files like API tokens and full notebook databases without any…
The problem
SiYuan's publish endpoint (port 6808, anonymous by design) registers a GET /assets/*path route. The GetAssetAbsPath function in kernel/model/assets.go runs a second url.PathUnescape call as a compatibility fallback when the first path lookup fails. This re-introduces the exact double-decode primitive that the CVE-2026-41894 patch eliminated on the /export/ route.
A request for /assets/%252e%252e/%252e%252e/conf/conf.json arrives with Gin already having decoded %25 to %, leaving literal %2e%2e strings in the path param. The fallback then decodes those to .., and filepath.Join resolves the final path to WorkspaceDir/conf/conf.json.
Because CheckAbsPathAccessableByPublishAccess only checks IsSubPath(DataDir, absPath) and returns true for anything outside DataDir (without ever calling IsSensitivePath), the file is served without restriction.
Proof of concept
A working proof-of-concept for CVE-2026-54066 in github.com/siyuan-note/siyuan/kernel, with the exact payload below.
curl -i "http://victim:6808/assets/%252e%252e/%252e%252e/conf/conf.json"Three independent flaws chain together. First, the url.PathUnescape fallback in assets.go:548 decodes %2e%2e to .. on the second pass, after Go's HTTP layer has already done the first decode. Second, CheckAbsPathAccessableByPublishAccess (publish_access.go:288) returns true for any resolved path outside DataDir, even paths still inside WorkspaceDir such as conf/conf.json and temp/*.db.
Third, the IsSensitivePath() denylist that the CVE-2026-41894 patch wired into the /export/ handler was never applied to the /assets/ handler at all.
The fix (commit 2d5d72223df4, released as v3.7.0) removes the url.PathUnescape fallback from GetAssetAbsPath, matching the approach taken on /export/. It also tightens CheckAbsPathAccessableByPublishAccess to require containment within DataDir and always invoke IsSensitivePath(), closing the fall-through path.
CWE-23 (Relative Path Traversal) and CWE-1188 (Insecure Default: publish mode on by default) both apply.
The fix
Upgrade SiYuan to v3.7.0 or later. The patch removes the redundant url.PathUnescape fallback in kernel/model/assets.go, tightens the publish-access gate to require paths inside DataDir, and ensures IsSensitivePath() is always evaluated for /assets/ requests.
If upgrading immediately is not possible, disable publish mode (conf.publish.enable = false) until the update is applied.
Reported by 0x_Akoko.
Related research
- critical · 9.9CVE-2026-54067CVE-2026-54067: SiYuan Stored XSS to RCE via CSS Snippet Style Tag Breakout
- critical · 9.9CVE-2026-54158CVE-2026-54158: SiYuan Stored XSS to RCE via Unescaped Attribute-View Cell Rendering
- critical · 9.9CVE-2026-50551CVE-2026-50551: SiYuan Stored XSS to RCE via Attribute View Asset Cell
- high · 7.1CVE-2026-54070CVE-2026-54070: SiYuan Stored XSS via Bazaar README Event Handler Bypass