# SiYuan: Path Traversal via Double URL Encoding in /assets/*path (publish mode arbitrary file─read), Incomplete fix of CVE-2026-41894

SiYuan publish mode /assets route double-decodes %252e%252e, letting unauthenticated attackers read conf.json, SQLite DBs and logs inside WorkspaceDir.

- Payout: Research
- Severity: high
- Program: SiYuan
- Found by: aslein1413-sys
- Disclosed: 2026-07-10
- CVE: CVE-2026-54066
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-p4m3-mgmm-c664
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-p4m3-mgmm-c664
- Tags: area:web domain:web impact:ato impact:data-leak kind:research platform:ghsa sev:high tech:go technique:incomplete-fix technique:source-review vuln:path-traversal

**Target:** SiYuan kernel publish-mode HTTP endpoint /assets/*path (port 6808)
**Root cause:** GetAssetAbsPath re-decodes the path with url.PathUnescape as a fallback, reintroducing double-decode; CheckAbsPathAccessableByPublishAccess returns true for any path outside DataDir (but inside WorkspaceDir) and never calls IsSensitivePath.
**Steps:**
1. Enable publish mode (conf.publish.enable = true) on siyuan v3.6.5, port 6808.
2. Send `GET /assets/%252e%252e/%252e%252e/conf/conf.json` — Gin decodes once, leaving literal `%2e%2e` that path.Clean cannot collapse.
3. Fallback url.PathUnescape decodes to `..`, filepath.Join resolves to WorkspaceDir/conf/conf.json.
4. Publish access gate sees path outside DataDir and returns true; IsSensitivePath never runs.
5. http.ServeFile sees literal `%2e%2e` in URL.Path so containsDotDot passes; file returned HTTP 200, 10349 bytes.
6. Repeat for temp/siyuan.db, temp/blocktree.db, siyuan.log.
**How found:** Noticed the CVE-2026-41894 patch only sanitized /export/; tested the identical double-encoding primitive against the sibling /assets/ route and compared responses (200 vs 401 on /export/, 404 on single-encoded).
**Exploitation:** Unauthenticated attacker hits the publish endpoint with double-encoded traversal to read conf/conf.json (accessAuthCode SHA256, API token, sync credentials), SQLite notebook DBs and logs; leaked token/authCode escalates to authenticated kernel API read/write of all notebooks.
**Escalation:** Leaked accessAuthCode/API token grants authenticated kernel API access (full notebook read/write); sync credentials extend compromise beyond the host.
**Insight:** When a traversal patch lands on one route, replay the exact same encoded payload against every sibling file-serving route — here the second url.PathUnescape fallback and a DataDir-only subpath check left /assets/ wide open.

## Proof of concept (copied exactly from the original report)

**curl request reading conf.json via double encoding**

```
$ curl -i "http://victim:6808/assets/%252e%252e/%252e%252e/conf/conf.json"
  HTTP/1.1 200 OK
  Content-Length: 10349
  Content-Type: application/json
  ...
  {"appearance":{...},"editor":{...},"system":{...},"accessAuthCode":"<sha256>","api":{"token":"<api token>"}, ...}
```

**Patched /export/ route returns 401 comparison**

```
$ curl -i "http://victim:6808/export/%252e%252e/%252e%252e/conf/conf.json"
  HTTP/1.1 401 Unauthorized
```

