# Security Advisory — SiYuan MCP `asset.upload` Reads Arbitrary Absolute File Paths (Workspace Boundary Bypass) | Field | Value | |---|---| | **Disclosed by** | joysinleung (`joysinleung@gmail.com`) | | **Report date** | 2026-08-13 | | **Product** | SiYuan (思源笔记) — `siyuan-note/siyuan` | | **Go...
Full CISO analysis pending enrichment.
What systems are affected?
| Package | Ecosystem | Vulnerable Range | Patched |
|---|---|---|---|
| github.com/siyuan-note/siyuan/kernel | go | < 0.0.0-20260813142104-b26a4a307b8a | 0.0.0-20260813142104-b26a4a307b8a |
Do you use github.com/siyuan-note/siyuan/kernel? You're affected.
How severe is it?
What is the attack surface?
What should I do?
Patch available
Update github.com/siyuan-note/siyuan/kernel to version 0.0.0-20260813142104-b26a4a307b8a
Which compliance frameworks are affected?
Compliance analysis pending. Sign in for full compliance mapping when available.
Frequently Asked Questions
What is GHSA-p23f-cm6q-2qp8?
# Security Advisory — SiYuan MCP `asset.upload` Reads Arbitrary Absolute File Paths (Workspace Boundary Bypass) | Field | Value | |---|---| | **Disclosed by** | joysinleung (`joysinleung@gmail.com`) | | **Report date** | 2026-08-13 | | **Product** | SiYuan (思源笔记) — `siyuan-note/siyuan` | | **Go module** | `github.com/siyuan-note/siyuan/kernel` | | **Affected versions** | `<= 3.8.0` (latest release at report time; statically confirmed on v3.8.0) | | **Patched versions** | 3.8.1 | | **Component** | `kernel/mcp/tools/asset.go` (`assetUpload`), `kernel/model/upload.go` (`InsertLocalAssets`) | | **Relationship to prior advisory** | **Residual of CVE-2026-66012** (GHSA-cvhv-7xhj-xjp8) MCP remediation. See §Relationship. | | **EPSS (exploitation probability)** | Low. Requires the AI Agent to invoke `asset.upload` and the user to approve the (category-level) confirmation; reachable via prompt-injection of the agent. | | **KEV (CISA Known Exploited)** | No (not listed in CISA KEV at report time). | | **Default-config reachable** | **Partial** — requires the Agent/MCP surface to be configured (admin) and a user approval click; the boundary check itself is entirely absent, so any approved upload reads outside the workspace. | --- ## Summary SiYuan exposes a native MCP tool `asset` → `asset.upload`. Its `files` argument is documented as a **comma-separated list of absolute file paths**. The handler (`kernel/mcp/tools/asset.go:195`) only normalizes each entry with `filepath.Abs(...)` — it performs **no workspace boundary check** (`IsSubPath`) and **no sensitive-path check** (`IsSensitivePath`). The downstream `model.InsertLocalAssets` (`kernel/model/upload.go:97`) then `os.Open`s each path and copies its bytes into the workspace `assets/` directory. Every other AI-plane file primitive in SiYuan is workspace-constrained: - `file` / `unzip` tools resolve paths via `resolvePath` (workspace-relative, escape-proof). `asset.upload` is the **only** AI tool that accepts arbitrary absolute paths with zero boundary validation. An attacker who can steer the Agent (prompt injection) can induce it to upload sensitive files — e.g. `/Users/victim/.ssh/id_rsa`, `~/.aws/credentials`, `/etc/passwd` — into the workspace, from where they are reachable via notes / export / sync. ## Relationship to Prior Advisories - **CVE-2026-66012** (GHSA-cvhv-7xhj-xjp8) remediated the MCP endpoint by requiring `CheckAdminRole` on `/mcp` and adding `refuseToAccess` for `conf/conf.json` in the *file* tool. However, the **`asset.upload` tool was never given a workspace boundary check** — it still accepts and reads any absolute path. This is a **residual boundary omission** from that remediation: the admin gate restricts *who* may call MCP, but does not constrain *which files* `asset.upload` may read. We report it as the unpatched half of the MCP file-surface hardening. ## Affected Version Statically confirmed on the latest release **v3.8.0** (tag `v3.8.0`, commit `251596fc0`): - `kernel/mcp/tools/asset.go:195` `assetUpload`: `abs, _ := filepath.Abs(strings.TrimSpace(f))` — normalization only. - `kernel/model/upload.go:97` `InsertLocalAssets`: iterates the path list and `os.Open(assetAbsPath)` → `writeAssetFile(writePath, ...)` into `assetsDirPath`; the `IsSubPath(assetsDirPath, assetAbsPath)` check at line 130 is **only a dedup guard**, not a boundary limit. No workspace/external restriction is applied. ## Component - `kernel/mcp/tools/asset.go:195` — `assetUpload` (`files` arg → `filepath.Abs` only). - `kernel/model/upload.go:97` — `InsertLocalAssets` (`os.Open` + copy to `assets/`). - Contrast (safe): `kernel/mcp/tools/unzip.go:52` `unzipHandler` uses `resolvePath` (workspace-relative, escape-proof). ## Attack Vector **AI Agent / prompt injection.** Victim lets the Agent process attacker-controlled web/note content → agent is induced to call `asset.upload` with `/Users/victim/.ssh/id_rsa` (or similar) as a `files` entry. The user sees a **category-level** confirmation ("upload asset") that does **not** display the specific source path, so the approval is effectively blind to the actual file being read. Once approved, the file is copied into the workspace and exfiltrated via notes/export/sync. No admin role is required beyond the existing Agent/MCP configuration. ## Proof of Concept ```jsonc // Agent tool call (asset.upload), attacker-influenced files argument: { "id": "<someBlockID>", "files": "/Users/victim/.ssh/id_rsa,/Users/victim/.aws/credentials,/etc/passwd" } ``` Handler flow: 1. `filepath.Abs("/Users/victim/.ssh/id_rsa")` → `/Users/victim/.ssh/id_rsa` (no `IsSubPath`/`IsSensitivePath` rejection). 2. `InsertLocalAssets` → `os.Open` → bytes copied into `<workspace>/data/assets/...`. 3. The private key is now readable via the workspace file API / export / sync. ## Impact Confidentiality breach: arbitrary local file read (including SSH keys, cloud credentials, OS secrets) through the AI plane. Integrity/availability not directly impacted. Severity is moderated by the required user approval step, but the approval dialog does not reveal the real source path, so the user cannot make an informed decision. ## Scope Reachable only when the Agent/MCP surface is configured and the user approves the upload action. The boundary check is absent regardless of config — any approved upload reads outside the workspace. ## Remediation 1. **Add boundary enforcement in `assetUpload`**: reject any entry where `!util.IsSubPath(util.WorkspaceDir, abs)` or `util.IsSensitivePath(abs)` (same checks used elsewhere). 2. **Show the real source path in the confirmation dialog** so the user can make an informed decision (currently the `LocalWrite` gate is action-category based and hides the specific path). 3. Mirror the `resolvePath` workspace-relative constraint already applied to the `file` / `unzip` tools. > Note: patch authored against v3.8.0 source; not compiled into a full SiYuan release build. Provided for the maintainer to validate in CI. --- ## Appendix: Suggested Patch (F10) ```diff diff --git a/kernel/mcp/tools/asset.go b/kernel/mcp/tools/asset.go index aaa..bbb 100644 --- a/kernel/mcp/tools/asset.go +++ b/kernel/mcp/tools/asset.go @@ -195,8 +195,16 @@ func assetUpload(args map[string]any) (CallToolResult, error) { fileList := strings.Split(filesStr, ",") for i, f := range fileList { abs, err := filepath.Abs(strings.TrimSpace(f)) - if err != nil { + if err != nil { return CallToolResult{}, err } + // 边界校验:仅允许工作区内的资产,拒绝任意绝对路径越界读 + if !util.IsSubPath(util.WorkspaceDir, abs) || util.IsSensitivePath(abs) { + ret.Code = -1 + ret.Msg = fmt.Sprintf("asset path %s is outside the workspace or sensitive", abs) + return CallToolResult{}, fmt.Errorf("asset path outside workspace: %s", abs) + } fileList[i] = abs } succMap, err := model.InsertLocalAssets(id, fileList, true) ```
Is GHSA-p23f-cm6q-2qp8 actively exploited?
No confirmed active exploitation of GHSA-p23f-cm6q-2qp8 has been reported, but organizations should still patch proactively.
How to fix GHSA-p23f-cm6q-2qp8?
Update to patched version: github.com/siyuan-note/siyuan/kernel 0.0.0-20260813142104-b26a4a307b8a.
What is the CVSS score for GHSA-p23f-cm6q-2qp8?
GHSA-p23f-cm6q-2qp8 has a CVSS v3.1 base score of 5.7 (MEDIUM).
What are the technical details?
Original Advisory
# Security Advisory — SiYuan MCP `asset.upload` Reads Arbitrary Absolute File Paths (Workspace Boundary Bypass) | Field | Value | |---|---| | **Disclosed by** | joysinleung (`joysinleung@gmail.com`) | | **Report date** | 2026-08-13 | | **Product** | SiYuan (思源笔记) — `siyuan-note/siyuan` | | **Go module** | `github.com/siyuan-note/siyuan/kernel` | | **Affected versions** | `<= 3.8.0` (latest release at report time; statically confirmed on v3.8.0) | | **Patched versions** | 3.8.1 | | **Component** | `kernel/mcp/tools/asset.go` (`assetUpload`), `kernel/model/upload.go` (`InsertLocalAssets`) | | **Relationship to prior advisory** | **Residual of CVE-2026-66012** (GHSA-cvhv-7xhj-xjp8) MCP remediation. See §Relationship. | | **EPSS (exploitation probability)** | Low. Requires the AI Agent to invoke `asset.upload` and the user to approve the (category-level) confirmation; reachable via prompt-injection of the agent. | | **KEV (CISA Known Exploited)** | No (not listed in CISA KEV at report time). | | **Default-config reachable** | **Partial** — requires the Agent/MCP surface to be configured (admin) and a user approval click; the boundary check itself is entirely absent, so any approved upload reads outside the workspace. | --- ## Summary SiYuan exposes a native MCP tool `asset` → `asset.upload`. Its `files` argument is documented as a **comma-separated list of absolute file paths**. The handler (`kernel/mcp/tools/asset.go:195`) only normalizes each entry with `filepath.Abs(...)` — it performs **no workspace boundary check** (`IsSubPath`) and **no sensitive-path check** (`IsSensitivePath`). The downstream `model.InsertLocalAssets` (`kernel/model/upload.go:97`) then `os.Open`s each path and copies its bytes into the workspace `assets/` directory. Every other AI-plane file primitive in SiYuan is workspace-constrained: - `file` / `unzip` tools resolve paths via `resolvePath` (workspace-relative, escape-proof). `asset.upload` is the **only** AI tool that accepts arbitrary absolute paths with zero boundary validation. An attacker who can steer the Agent (prompt injection) can induce it to upload sensitive files — e.g. `/Users/victim/.ssh/id_rsa`, `~/.aws/credentials`, `/etc/passwd` — into the workspace, from where they are reachable via notes / export / sync. ## Relationship to Prior Advisories - **CVE-2026-66012** (GHSA-cvhv-7xhj-xjp8) remediated the MCP endpoint by requiring `CheckAdminRole` on `/mcp` and adding `refuseToAccess` for `conf/conf.json` in the *file* tool. However, the **`asset.upload` tool was never given a workspace boundary check** — it still accepts and reads any absolute path. This is a **residual boundary omission** from that remediation: the admin gate restricts *who* may call MCP, but does not constrain *which files* `asset.upload` may read. We report it as the unpatched half of the MCP file-surface hardening. ## Affected Version Statically confirmed on the latest release **v3.8.0** (tag `v3.8.0`, commit `251596fc0`): - `kernel/mcp/tools/asset.go:195` `assetUpload`: `abs, _ := filepath.Abs(strings.TrimSpace(f))` — normalization only. - `kernel/model/upload.go:97` `InsertLocalAssets`: iterates the path list and `os.Open(assetAbsPath)` → `writeAssetFile(writePath, ...)` into `assetsDirPath`; the `IsSubPath(assetsDirPath, assetAbsPath)` check at line 130 is **only a dedup guard**, not a boundary limit. No workspace/external restriction is applied. ## Component - `kernel/mcp/tools/asset.go:195` — `assetUpload` (`files` arg → `filepath.Abs` only). - `kernel/model/upload.go:97` — `InsertLocalAssets` (`os.Open` + copy to `assets/`). - Contrast (safe): `kernel/mcp/tools/unzip.go:52` `unzipHandler` uses `resolvePath` (workspace-relative, escape-proof). ## Attack Vector **AI Agent / prompt injection.** Victim lets the Agent process attacker-controlled web/note content → agent is induced to call `asset.upload` with `/Users/victim/.ssh/id_rsa` (or similar) as a `files` entry. The user sees a **category-level** confirmation ("upload asset") that does **not** display the specific source path, so the approval is effectively blind to the actual file being read. Once approved, the file is copied into the workspace and exfiltrated via notes/export/sync. No admin role is required beyond the existing Agent/MCP configuration. ## Proof of Concept ```jsonc // Agent tool call (asset.upload), attacker-influenced files argument: { "id": "<someBlockID>", "files": "/Users/victim/.ssh/id_rsa,/Users/victim/.aws/credentials,/etc/passwd" } ``` Handler flow: 1. `filepath.Abs("/Users/victim/.ssh/id_rsa")` → `/Users/victim/.ssh/id_rsa` (no `IsSubPath`/`IsSensitivePath` rejection). 2. `InsertLocalAssets` → `os.Open` → bytes copied into `<workspace>/data/assets/...`. 3. The private key is now readable via the workspace file API / export / sync. ## Impact Confidentiality breach: arbitrary local file read (including SSH keys, cloud credentials, OS secrets) through the AI plane. Integrity/availability not directly impacted. Severity is moderated by the required user approval step, but the approval dialog does not reveal the real source path, so the user cannot make an informed decision. ## Scope Reachable only when the Agent/MCP surface is configured and the user approves the upload action. The boundary check is absent regardless of config — any approved upload reads outside the workspace. ## Remediation 1. **Add boundary enforcement in `assetUpload`**: reject any entry where `!util.IsSubPath(util.WorkspaceDir, abs)` or `util.IsSensitivePath(abs)` (same checks used elsewhere). 2. **Show the real source path in the confirmation dialog** so the user can make an informed decision (currently the `LocalWrite` gate is action-category based and hides the specific path). 3. Mirror the `resolvePath` workspace-relative constraint already applied to the `file` / `unzip` tools. > Note: patch authored against v3.8.0 source; not compiled into a full SiYuan release build. Provided for the maintainer to validate in CI. --- ## Appendix: Suggested Patch (F10) ```diff diff --git a/kernel/mcp/tools/asset.go b/kernel/mcp/tools/asset.go index aaa..bbb 100644 --- a/kernel/mcp/tools/asset.go +++ b/kernel/mcp/tools/asset.go @@ -195,8 +195,16 @@ func assetUpload(args map[string]any) (CallToolResult, error) { fileList := strings.Split(filesStr, ",") for i, f := range fileList { abs, err := filepath.Abs(strings.TrimSpace(f)) - if err != nil { + if err != nil { return CallToolResult{}, err } + // 边界校验:仅允许工作区内的资产,拒绝任意绝对路径越界读 + if !util.IsSubPath(util.WorkspaceDir, abs) || util.IsSensitivePath(abs) { + ret.Code = -1 + ret.Msg = fmt.Sprintf("asset path %s is outside the workspace or sensitive", abs) + return CallToolResult{}, fmt.Errorf("asset path outside workspace: %s", abs) + } fileList[i] = abs } succMap, err := model.InsertLocalAssets(id, fileList, true) ```
Weaknesses (CWE)
CWE-22 — Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal'): 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.
- [Implementation] Assume all input is malicious. Use an "accept known good" input validation strategy, i.e., use a list of acceptable inputs that strictly conform to specifications. Reject any input that does not strictly conform to specifications, or transform it into something that does. When performing input validation, consider all potentially relevant properties, including length, type of input, the full range of acceptable values, missing or extra inputs, syntax, consistency across related fields, and conformance to business rules. As an example of business rule logic, "boat" may be syntactically valid because it only contains alphanumeric characters, but it is not valid if the input is only expected to contain colors such as "red" or "blue." Do not rely exclusively on looking for malicious or malformed inputs. This is likely to miss at least one undesirable input, especially if the code's environment changes. This can give attackers enough room to bypass the intended validation. However, denylis
- [Architecture and Design] For any security checks that are performed on the client side, ensure that these checks are duplicated on the server side, in order to avoid CWE-602. Attackers can bypass the client-side checks by modifying values after the checks have been performed, or by changing the client to remove the client-side checks entirely. Then, these modified values would be submitted to the server.
Source: MITRE CWE corpus.
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:N/A:N