CVE-2026-69086: SiYuan Path Traversal Vulnerability
HIGHCVE-2026-69086 is a HIGH (CVSS 7.7) path traversal vulnerability (CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')) affecting SiYuan libraries in the Go ecosystem. The issue involves path traversal via unvalidated avid in renderattributeview/av read endpoints : reader-reachable cross-scope attribute-view disclosure. Remediation is available by upgrading affected packages to version 0.0.0-20260720151813-0f5a0e7c67b0.
CVE-2026-69086 at a Glance
Security Impact
CVSS: 7.7
The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N).
How to Fix CVE-2026-69086
To remediate this vulnerability, upgrade all affected SiYuan dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
Upgrade github.com/siyuan-note/siyuan/kernel to 0.0.0-20260720151813-0f5a0e7c67b0
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
inspect dependencies for github.com/siyuan-note/siyuan/kernel
How to Check if You Are Affected
To determine whether your application is affected by CVE-2026-69086, inspect your project dependencies for SiYuan:
inspect dependencies for github.com/siyuan-note/siyuan/kernel
Analyze the terminal output to locate any version of SiYuan in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 0.0.0-20260720151813-0f5a0e7c67b0), your application is affected.
Note: Verify declared build manifests and deployed runtime container environments.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal'), where the product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory. In SiYuan, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
CVE-2026-69086 Affected SiYuan Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
github.com/siyuan-note/siyuan/kernel
|
Go |
< 0.0.0-20260720151813-0f5a0e7c67b0
|
0.0.0-20260720151813-0f5a0e7c67b0
|
References
Frequently Asked Questions
What is CVE-2026-69086?
Which SiYuan versions are affected?
What version fixes CVE-2026-69086?
Is github.com/siyuan-note/siyuan/kernel affected?
What is the CVSS score of CVE-2026-69086?
What CWE is associated with CVE-2026-69086?
How can I check Go dependencies for CVE-2026-69086?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-7hm9-v7vf-7g4w",
"modified" : "2026-09-03T20:34:12Z",
"published" : "2026-09-03T20:34:12Z",
"aliases" : [ "CVE-2026-69086" ],
"summary" : "SiYuan: Path Traversal via unvalidated avID in RenderAttributeView/AV read endpoints : reader-reachable cross-scope attribute-view disclosure",
"details" : "**CVE:** This vulnerability corresponds to [CVE-2026-69086](https://nvd.nist.gov/vuln/detail/CVE-2026-69086).\n\n### Summary\n\nFour attribute-view read endpoints build a filesystem path from a caller-controlled `id`/`avID` and read it without confining the result to the attribute-view storage directory (`DataDir/storage/av/`). On the load (file-exists) code path there is no boundary check, so an `avID` containing `../` segments escapes `storage/av/` and causes the kernel to read a `.json` file elsewhere in the workspace.\n\nThe endpoints require only `CheckAuth`, which the publish service's `RoleReader` token satisfies; when `Publish.Auth.Enable` is `false` the publish proxy uses the anonymous account, making the surface reachable with no credentials.\n\n### Details\n\nAffected endpoints (all gated by `CheckAuth` only, no `CheckAdminRole`):\n\n- `POST /api/av/renderAttributeView` → `arg[\"id\"]`\n- `POST /api/av/getAttributeViewKeysByID` → `arg[\"avID\"]`\n- `POST /api/av/getAttributeViewKeys` → `arg[\"id\"]`\n- `POST /api/av/getCurrentAttrViewImages` → `arg[\"id\"]`\n\nIn `model.RenderAttributeView` (`model/attribute_view_render.go`), the only identifier guard `ast.IsNodeIDPattern(avID)` sits **inside** the `if !filelock.IsExist(existPath)` (create) branch:\n\n```go\nexistPath = GetAttributeViewDataPath(avID) // path built from avID, no check\nif !filelock.IsExist(existPath) { // NOT-EXIST / CREATE branch\n if !createIfNotExist {\n return // NotFound\n }\n if !ast.IsNodeIDPattern(avID) { // <-- ONLY id guard, create branch only\n return ErrInvalidID\n }\n // ... create ...\n}\nattrView, err = av.ParseAttributeView(avID) // LOAD runs unconditionally\n```\n\nWhen the traversal `avID` resolves to a file that already exists, the `!filelock.IsExist(...)` condition is `false`, the entire block (including the line with `ast.IsNodeIDPattern`) is skipped, and control falls straight through to `av.ParseAttributeView(avID)`. That function rebuilds the path via `filepath.Join(DataDir, \"storage\", \"av\", avID+\".json\")` and calls `filelock.ReadFile` with no `filepath.Rel` / `IsSubPath` / `..` rejection:\n\n```go\n// av.ParseAttributeView -> attributeViewDataPathByBox / GetAttributeViewDataPath\navJSONPath = filepath.Join(DataDir, \"storage\", \"av\", avID+\".json\") // no boundary check\n// -> parseAttributeViewByPathInBox(avJSONPath, boxID)\ndata, _ = filelock.ReadFile(avJSONPath) // SINK\n```\n\n`filepath.Join` cleans the path but does **not** reject `..` segments, so it provides no containment. The three `getAttributeView*` endpoints call `ParseAttributeView` with no create branch at all, so they never even reach the `ast.IsNodeIDPattern` check same defect, same auth tier.\n\nThe root cause is that identifier validation is placed on a single code branch rather than confining the load to the AV base directory, so the load path reads a caller-controlled location.\n\n### PoC\n\n**Precondition:** publish mode enabled (default port `6808`); reachable by a `RoleReader` publish token, or anonymously when `Publish.Auth.Enable` is `false`.\n\nA request to `/api/av/renderAttributeView` with an `id` composed of `../` path segments that resolves to an existing `.json` file outside `DataDir/storage/av/` causes that file to be read and parsed instead of being rejected, because the identifier validation is only reached on the not-exist/create branch.\n\nI have withheld the exact encoded `id` value from this draft to avoid publishing a live traversal against internet-exposed publish instances. I'm happy to provide the precise value and a screenshot privately in this thread on request.\n\n### Impact\n\nAn authenticated publish `RoleReader` or an anonymous client when publish auth is disabled can cause the kernel to read `.json` files outside the attribute-view directory. Because the loaded file is unmarshalled into the attribute-view structure, the reliable primitives are:\n\n1. Disclosure of attribute-view (database) content from other scopes/notebooks the reader is not authorized to see.\n2. A `.json`-path existence oracle for arbitrary workspace locations.\n\nFiles not conforming to the AV schema are read but reflect little content, and the `.json` suffix is force-appended, so this is **not** a general arbitrary-file read. No admin role, CSRF token, or write permission is required.\n\n### Suggested fix\n\nValidate `avID` with `ast.IsNodeIDPattern` before path construction on **all** branches (move it ahead of `FindAttributeViewPath` / `GetAttributeViewDataPath`), or preferably, so every caller inherits it confine at the sink: in `attributeViewDataPathByBox` / `GetAttributeViewDataPath`, compute the joined path and reject it unless `filepath.Rel(avBaseDir, cleaned)` stays within `avBaseDir` (no leading `..`). Sink-side confinement also covers the three `getAttributeView*` endpoints that never reach the create-branch guard.",
"severity" : [ {
"type" : "CVSS_V3",
"score" : "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "Go",
"name" : "github.com/siyuan-note/siyuan/kernel"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"fixed" : "0.0.0-20260720151813-0f5a0e7c67b0"
} ]
} ]
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/siyuan-note/siyuan/security/advisories/GHSA-7hm9-v7vf-7g4w"
}, {
"type" : "ADVISORY",
"url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-69086"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/siyuan-note/siyuan"
}, {
"type" : "WEB",
"url" : "https://www.vulncheck.com/advisories/siyuan-before-path-traversal-via-unvalidated-avid"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-22" ],
"severity" : "HIGH",
"github_reviewed" : true,
"github_reviewed_at" : "2026-09-03T20:34:12Z",
"nvd_published_at" : null
}
}