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 log
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