GHSA-P23F-CM6Q-2QP8
Vulnerability from github – Published: 2026-10-02 23:16 – Updated: 2026-10-02 23:16Security 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.Opens 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
CheckAdminRoleon/mcpand addingrefuseToAccessforconf/conf.jsonin the file tool. However, theasset.uploadtool 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 filesasset.uploadmay 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(filesarg →filepath.Absonly).kernel/model/upload.go:97—InsertLocalAssets(os.Open+ copy toassets/).- Contrast (safe):
kernel/mcp/tools/unzip.go:52unzipHandlerusesresolvePath(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
// 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
- Add boundary enforcement in
assetUpload: reject any entry where!util.IsSubPath(util.WorkspaceDir, abs)orutil.IsSensitivePath(abs)(same checks used elsewhere). - Show the real source path in the confirmation dialog so the user can make an informed decision (currently the
LocalWritegate is action-category based and hides the specific path). - Mirror the
resolvePathworkspace-relative constraint already applied to thefile/unziptools.
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 --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)
{
"affected": [
{
"package": {
"ecosystem": "Go",
"name": "github.com/siyuan-note/siyuan/kernel"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.0.0-20260813142104-b26a4a307b8a"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [],
"database_specific": {
"cwe_ids": [
"CWE-22"
],
"github_reviewed": true,
"github_reviewed_at": "2026-10-02T23:16:56Z",
"nvd_published_at": null,
"severity": "MODERATE"
},
"details": "# Security Advisory \u2014 SiYuan MCP `asset.upload` Reads Arbitrary Absolute File Paths (Workspace Boundary Bypass)\n\n| Field | Value |\n|---|---|\n| **Disclosed by** | joysinleung (`joysinleung@gmail.com`) |\n| **Report date** | 2026-08-13 |\n| **Product** | SiYuan (\u601d\u6e90\u7b14\u8bb0) \u2014 `siyuan-note/siyuan` |\n| **Go module** | `github.com/siyuan-note/siyuan/kernel` |\n| **Affected versions** | `\u003c= 3.8.0` (latest release at report time; statically confirmed on v3.8.0) |\n| **Patched versions** | 3.8.1 |\n| **Component** | `kernel/mcp/tools/asset.go` (`assetUpload`), `kernel/model/upload.go` (`InsertLocalAssets`) |\n| **Relationship to prior advisory** | **Residual of CVE-2026-66012** (GHSA-cvhv-7xhj-xjp8) MCP remediation. See \u00a7Relationship. |\n| **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. |\n| **KEV (CISA Known Exploited)** | No (not listed in CISA KEV at report time). |\n| **Default-config reachable** | **Partial** \u2014 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. |\n\n---\n\n## Summary\n\nSiYuan exposes a native MCP tool `asset` \u2192 `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(...)` \u2014 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.\n\nEvery other AI-plane file primitive in SiYuan is workspace-constrained:\n- `file` / `unzip` tools resolve paths via `resolvePath` (workspace-relative, escape-proof).\n\n`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 \u2014 e.g. `/Users/victim/.ssh/id_rsa`, `~/.aws/credentials`, `/etc/passwd` \u2014 into the workspace, from where they are reachable via notes / export / sync.\n\n## Relationship to Prior Advisories\n\n- **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** \u2014 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.\n\n## Affected Version\n\nStatically confirmed on the latest release **v3.8.0** (tag `v3.8.0`, commit `251596fc0`):\n- `kernel/mcp/tools/asset.go:195` `assetUpload`: `abs, _ := filepath.Abs(strings.TrimSpace(f))` \u2014 normalization only.\n- `kernel/model/upload.go:97` `InsertLocalAssets`: iterates the path list and `os.Open(assetAbsPath)` \u2192 `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.\n\n## Component\n\n- `kernel/mcp/tools/asset.go:195` \u2014 `assetUpload` (`files` arg \u2192 `filepath.Abs` only).\n- `kernel/model/upload.go:97` \u2014 `InsertLocalAssets` (`os.Open` + copy to `assets/`).\n- Contrast (safe): `kernel/mcp/tools/unzip.go:52` `unzipHandler` uses `resolvePath` (workspace-relative, escape-proof).\n\n## Attack Vector\n\n**AI Agent / prompt injection.** Victim lets the Agent process attacker-controlled web/note content \u2192 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.\n\n## Proof of Concept\n\n```jsonc\n// Agent tool call (asset.upload), attacker-influenced files argument:\n{\n \"id\": \"\u003csomeBlockID\u003e\",\n \"files\": \"/Users/victim/.ssh/id_rsa,/Users/victim/.aws/credentials,/etc/passwd\"\n}\n```\nHandler flow:\n1. `filepath.Abs(\"/Users/victim/.ssh/id_rsa\")` \u2192 `/Users/victim/.ssh/id_rsa` (no `IsSubPath`/`IsSensitivePath` rejection).\n2. `InsertLocalAssets` \u2192 `os.Open` \u2192 bytes copied into `\u003cworkspace\u003e/data/assets/...`.\n3. The private key is now readable via the workspace file API / export / sync.\n\n## Impact\n\nConfidentiality 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.\n\n## Scope\n\nReachable only when the Agent/MCP surface is configured and the user approves the upload action. The boundary check is absent regardless of config \u2014 any approved upload reads outside the workspace.\n\n## Remediation\n\n1. **Add boundary enforcement in `assetUpload`**: reject any entry where `!util.IsSubPath(util.WorkspaceDir, abs)` or `util.IsSensitivePath(abs)` (same checks used elsewhere).\n2. **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).\n3. Mirror the `resolvePath` workspace-relative constraint already applied to the `file` / `unzip` tools.\n\n\u003e Note: patch authored against v3.8.0 source; not compiled into a full SiYuan release build. Provided for the maintainer to validate in CI.\n\n---\n\n## Appendix: Suggested Patch (F10)\n\n```diff\ndiff --git a/kernel/mcp/tools/asset.go b/kernel/mcp/tools/asset.go\nindex aaa..bbb 100644\n--- a/kernel/mcp/tools/asset.go\n+++ b/kernel/mcp/tools/asset.go\n@@ -195,8 +195,16 @@ func assetUpload(args map[string]any) (CallToolResult, error) {\n \tfileList := strings.Split(filesStr, \",\")\n \tfor i, f := range fileList {\n \t\tabs, err := filepath.Abs(strings.TrimSpace(f))\n-\t\tif err != nil {\n+\t\tif err != nil {\n \t\t\treturn CallToolResult{}, err\n \t\t}\n+\t\t// \u8fb9\u754c\u6821\u9a8c\uff1a\u4ec5\u5141\u8bb8\u5de5\u4f5c\u533a\u5185\u7684\u8d44\u4ea7\uff0c\u62d2\u7edd\u4efb\u610f\u7edd\u5bf9\u8def\u5f84\u8d8a\u754c\u8bfb\n+\t\tif !util.IsSubPath(util.WorkspaceDir, abs) || util.IsSensitivePath(abs) {\n+\t\t\tret.Code = -1\n+\t\t\tret.Msg = fmt.Sprintf(\"asset path %s is outside the workspace or sensitive\", abs)\n+\t\t\treturn CallToolResult{}, fmt.Errorf(\"asset path outside workspace: %s\", abs)\n+\t\t}\n \t\tfileList[i] = abs\n \t}\n \tsuccMap, err := model.InsertLocalAssets(id, fileList, true)\n```",
"id": "GHSA-p23f-cm6q-2qp8",
"modified": "2026-10-02T23:16:56Z",
"published": "2026-10-02T23:16:56Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/siyuan-note/siyuan/security/advisories/GHSA-p23f-cm6q-2qp8"
},
{
"type": "WEB",
"url": "https://github.com/siyuan-note/siyuan/commit/b26a4a307b8ae5067387ea76ce1fad6b7f4bf9e7"
},
{
"type": "PACKAGE",
"url": "https://github.com/siyuan-note/siyuan"
},
{
"type": "WEB",
"url": "https://github.com/siyuan-note/siyuan/releases/tag/v3.8.1"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:N/A:N",
"type": "CVSS_V3"
}
],
"summary": "SiYuan MCP asset.upload Reads Arbitrary Absolute File Paths (Workspace Boundary Bypass)"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.