high · 7.7CVE-2026-59832Sep 2, 2026

CVE-2026-59832: SiYuan Authenticated Path Traversal in /snippets/ Handler

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

An authenticated SiYuan user can read any file inside the workspace, including the kernel API token and full document database, by sending a percent-encoded directory traversal path to the /snippets/…

Packagegithub.com/siyuan-note/siyuan/kernel
Ecosystemgo
Affected< 0.0.0-20260704035520-68cc0f537dfa
Fixed in0.0.0-20260704035520-68cc0f537dfa
CVE-2026-59832: SiYuan Authenticated Path Traversal in /snippets/ Handler

The problem

The serveSnippets handler in kernel/server/serve.go builds a file path with a bare filepath.Join(util.SnippetsPath, filePath) and serves it via c.File(), with no subpath containment check and no sensitive-path denylist.

Sibling handlers (serveExport, serveAppearance, serveAssets) all carry IsSubPath or equivalent guards. serveSnippets was never updated. The route requires only a valid authenticated session, not an admin role, so any RoleEditor or RoleReader account can exploit it.

Proof of concept

A working proof-of-concept for CVE-2026-59832 in github.com/siyuan-note/siyuan/kernel, with the exact payload below.

http
GET /snippets/%2e%2e/%2e%2e/conf/conf.json HTTP/1.1
Host: <siyuan-host>:6806
Authorization: Token <any-valid-api-token>

# Also works for the full document database:
# GET /snippets/%2e%2e/%2e%2e/temp/siyuan.db
# And for host files outside the workspace:
# GET /snippets/%2e%2e/%2e%2e/%2e%2e/etc/passwd

Go's net/http percent-decodes URL.Path once before the gin handler sees it, so %2e%2e becomes ... strings.TrimPrefix strips the route prefix, leaving the attacker-controlled remainder, e.g. ../../conf/conf.json. The non-admin guard compares only the literal string "conf.json", which the traversal value does not match, so it does not fire.

filepath.Join then calls filepath.Clean, which resolves the .. segments. With util.SnippetsPath = WorkspaceDir/data/snippets, two .. hops resolve to WorkspaceDir/conf/conf.json. Because there is no IsSubPath check, the resolved path escapes the snippets root unchallenged and c.File() streams it to the caller.

The patch at commit 68cc0f537dfa adds the same gulu.File.IsSubPath(util.SnippetsPath, resolved) and util.IsSensitivePath guards that the serveExport handler received in the earlier bb481e1 fix. CWE-22, CWE-23.

The fix

Upgrade to SiYuan v3.7.1. The patch at commit 68cc0f537dfa4502496dfa794e71835421c25c09 adds gulu.File.IsSubPath(util.SnippetsPath, resolvedPath) and util.IsSensitivePath(resolvedPath) checks to serveSnippets, matching the containment guards already present in the sibling file-serving handlers.

Reported by Cavan Loughran, Celvex Group Inc..

References: [1][2][3][4][5]

Related research