GHSA-3cm4-ccvw-6xr6: SiYuan: history/repo-diff routes skip secret-file guard

GHSA-3cm4-ccvw-6xr6MEDIUM
Published October 5, 2026
CISO Take

SiYuan's kernel has two admin-only HTTP routes, /history/*path and /repo/diff/*path, that never call the new IsForbiddenAbsPath() guard added by GHSA-c8r8-95hg-mp34. An admin can therefore retrieve historical snapshot copies of data/.siyuan/publishAccess.json (plaintext publish-mode passwords) and data/templates/* even after the live files are protected. Exposure is limited by the high-privilege requirement (admin auth) and by the need for sensitive content to exist in a snapshot, so the CVSS is Medium (4.9). There is no known public exploit, no Nuclei template, no KEV listing, and EPSS data is unavailable. The package has about 3,047 downstream dependents and 62 prior CVEs, so the pattern of incomplete fixes is worth tracking. Upgrade to v3.8.1 or a build at or after pseudo-version 0.0.0-20260816034002-035bf9a8c311. Rotate any publish passwords that were ever stored in the workspace, and review who holds admin credentials.

Sources:GitHub AdvisoryNVD

What is the risk?

Medium risk. The attack is network-reachable and low complexity, but it needs administrator authentication (PR:H) and is read-only, so there is no integrity or availability impact. Real exposure depends on whether old publishAccess.json or template versions exist in history or repo snapshots. Impact is mainly credential disclosure, which could enable lateral access to published notebooks. There is no evidence of active exploitation, and no public exploit or scanner template exists. Exposure grows where SiYuan is internet-facing, shared across several admins, or used as an AI-assisted knowledge base (the package is classed as ai_tool and has an MCP file tool).

How does the attack unfold?

Initial Access
Attacker obtains valid admin credentials (accessAuthCode) for the SiYuan instance.
AML.T0012
Exploitation
Attacker requests /history/* or /repo/diff/* paths that bypass the IsForbiddenAbsPath() guard.
AML.T0037
Credential Access
Server returns historical publishAccess.json or template files containing plaintext secrets.
AML.T0055
Impact
Attacker reuses recovered publish passwords to access protected published notebooks and sensitive knowledge content.

What systems are affected?

PackageEcosystemVulnerable RangePatched
Jupyter Notebookgo< 0.0.0-20260816034002-035bf9a8c3110.0.0-20260816034002-035bf9a8c311
13.5KOpenSSF 5.83.0K dependentsPushed 11d ago79% patched~75d to patchFull package profile →

Do you use Jupyter Notebook? You're affected.

How severe is it?

CVSS 3.1
4.9 / 10
EPSS
N/A
Exploitation Status
No known exploitation
Sophistication
Trivial

What is the attack surface?

AVACPRUISCIA
AVNetwork
ACLow
PRHigh
UINone
SUnchanged
CHigh
INone
ANone

What should I do?

1 step
  1. 1) Upgrade SiYuan to v3.8.1 or later, which includes commit 035bf9a8c311. 2) Rotate all publish-mode passwords, the accessAuthCode, and any API tokens that have ever existed in the workspace. 3) Purge or prune historical snapshots and repo data that contain publishAccess.json or sensitive templates. 4) Restrict admin credentials, keep SiYuan off the public internet, and place it behind a VPN or reverse proxy with IP allowlisting. 5) Detection: monitor web and access logs for GET requests to /history/ and /repo/diff/ paths that reference publishAccess.json or the templates directory. 6) Audit any MCP or agent integrations that have admin-level access to the kernel.

How is it classified?

Which compliance frameworks are affected?

This CVE is relevant to:

ISO 42001
A.7.4-Quality and security of data for AI systems
NIST AI RMF
MANAGE 2.3-Respond to previously unknown risks and manage identified vulnerabilities
OWASP LLM Top 10
LLM02-Sensitive Information Disclosure

Frequently Asked Questions

What is GHSA-3cm4-ccvw-6xr6?

SiYuan's kernel has two admin-only HTTP routes, /history/*path and /repo/diff/*path, that never call the new IsForbiddenAbsPath() guard added by GHSA-c8r8-95hg-mp34. An admin can therefore retrieve historical snapshot copies of data/.siyuan/publishAccess.json (plaintext publish-mode passwords) and data/templates/* even after the live files are protected. Exposure is limited by the high-privilege requirement (admin auth) and by the need for sensitive content to exist in a snapshot, so the CVSS is Medium (4.9). There is no known public exploit, no Nuclei template, no KEV listing, and EPSS data is unavailable. The package has about 3,047 downstream dependents and 62 prior CVEs, so the pattern of incomplete fixes is worth tracking. Upgrade to v3.8.1 or a build at or after pseudo-version 0.0.0-20260816034002-035bf9a8c311. Rotate any publish passwords that were ever stored in the workspace, and review who holds admin credentials.

Is GHSA-3cm4-ccvw-6xr6 actively exploited?

No confirmed active exploitation of GHSA-3cm4-ccvw-6xr6 has been reported, but organizations should still patch proactively.

How to fix GHSA-3cm4-ccvw-6xr6?

1) Upgrade SiYuan to v3.8.1 or later, which includes commit 035bf9a8c311. 2) Rotate all publish-mode passwords, the accessAuthCode, and any API tokens that have ever existed in the workspace. 3) Purge or prune historical snapshots and repo data that contain publishAccess.json or sensitive templates. 4) Restrict admin credentials, keep SiYuan off the public internet, and place it behind a VPN or reverse proxy with IP allowlisting. 5) Detection: monitor web and access logs for GET requests to /history/ and /repo/diff/ paths that reference publishAccess.json or the templates directory. 6) Audit any MCP or agent integrations that have admin-level access to the kernel.

What systems are affected by GHSA-3cm4-ccvw-6xr6?

This vulnerability affects the following AI/ML architecture patterns: knowledge management platforms, agent frameworks (MCP tool servers), RAG knowledge sources, self-hosted AI note-taking tools.

What is the CVSS score for GHSA-3cm4-ccvw-6xr6?

GHSA-3cm4-ccvw-6xr6 has a CVSS v3.1 base score of 4.9 (MEDIUM).

What is the AI security impact?

Affected AI Architectures

knowledge management platformsagent frameworks (MCP tool servers)RAG knowledge sourcesself-hosted AI note-taking tools

MITRE ATLAS Techniques

AML.T0012Valid Accounts
AML.T0037Data from Local System
AML.T0055Unsecured Credentials

Compliance Controls Affected

ISO 42001: A.7.4
NIST AI RMF: MANAGE 2.3
OWASP LLM Top 10: LLM02

What are the technical details?

Original Advisory

### Summary `GHSA-c8r8-95hg-mp34` added a centralized guard, `util.IsForbiddenAbsPath()`, specifically to block access to a small set of sensitive files: `conf/conf.json` (plaintext `accessAuthCode`/API token/cookie key), `data/snippets/conf.json`, the entire `data/templates/` directory, and `data/.siyuan/publishAccess.json` (plaintext publish-mode passwords). It was applied to `kernel/api/file.go` and `kernel/mcp/tools/file.go`. Two other routes in the same server that serve arbitrary files by path, `/history/*path` and `/repo/diff/*path`, construct their target paths independently and were not updated to call this new guard. Since the repo/history snapshot system's tracked root is `data/` (confirmed by `getSyncIgnoreLines()`, whose ignore file lives at `data/.siyuan/syncignore` with entries relative to `data/`), both `data/.siyuan/publishAccess.json` and `data/templates/*` fall within the scope that can legitimately be captured in historical snapshots, meaning a prior version of either file can exist in `util.HistoryDir`/the repo-diff temp checkout even after the live file has been protected by the new guard. This is CWE-862 (Missing Authorization) applied to a very recently introduced protection mechanism. ### Details `kernel/server/serve.go`, `/history/*path` (around line 994): ```go ginServer.GET("/history/*path", model.CheckAuth, model.CheckAdminRole, func(context *gin.Context) { p := filepath.Join(util.HistoryDir, context.Param("path")) // 加密笔记本的历史是密文(.sy/assets/AV),需先解密再输出 if serveEncryptedHistory(context, p) { return } secureAssetContentHeaders(context, p, p) http.ServeFile(context.Writer, context.Request, p) }) ``` No call to `util.IsForbiddenAbsPath(p)` anywhere in this handler. `kernel/server/serve.go`, `/repo/diff/*path` (around line 1241): ```go ginServer.GET("/repo/diff/*path", model.CheckAuth, model.CheckAdminRole, func(context *gin.Context) { requestPath := filepath.Clean(context.Param("path")) if strings.Contains(requestPath, "..") { context.Status(http.StatusUnauthorized) return } ... p := filepath.Join(repoDiffBaseDir, requestPath) if !gulu.File.IsSubPath(repoDiffBaseDir, p) { context.Status(http.StatusUnauthorized) return } http.ServeFile(context.Writer, context.Request, p) }) ``` This route does have its own traversal protection (`..` rejection and `IsSubPath` containment within `repoDiffBaseDir`), but that only prevents escaping the diff-checkout directory, it does nothing to prevent retrieving a *legitimately checked-out historical copy* of `publishAccess.json` or a templates file from within that directory, which is exactly what the new guard exists to prevent regardless of which directory the copy currently sits in. `util.IsForbiddenAbsPath()` itself (`kernel/util/path_guard.go`, introduced by the referenced fix) confirms the intended scope: ```go // 禁止访问 data/.siyuan/publishAccess.json(含发布模式明文访问密码) publishAccessPath := NormalizeAndResolve(filepath.Join(DataDir, ".siyuan", "publishAccess.json")) if fileNorm == publishAccessPath { return true } ``` and ```go // 禁止访问 data/templates 目录(含目录本身及其全部子路径) templatesBase := NormalizeAndResolve(filepath.Join(DataDir, "templates")) if fileNorm == templatesBase || gulu.File.IsSubPath(templatesBase, fileNorm) { return true } ``` Both are paths within `data/`, the same root the sync/history/repo system tracks. ### Step-by-step reproduction 1. As the workspace admin, enable Publish with a password on at least one notebook (creating `data/.siyuan/publishAccess.json` with a plaintext password), then let a sync/backup snapshot capture this state (or check whether local history capture already covers `data/.siyuan/` in the deployed version). 2. Change or remove the publish password, so the live `publishAccess.json` no longer contains the old plaintext password the new guard is meant to hide, going forward. 3. As the admin, request the historical/diff version instead of the live file: ```bash curl -s http://<target>:6806/history/<snapshot-path-to-publishAccess.json> \ -u "<workspaceName>:<accessAuthCode>" curl -s http://<target>:6806/repo/diff/<diff-path-to-publishAccess.json> \ -u "<workspaceName>:<accessAuthCode>" ``` 4. Expected if consistently protected, matching the behavior the new guard already provides on the live-file endpoints: rejected. Observed: neither handler calls `IsForbiddenAbsPath`, so the historical copy is served if it exists in that location. *(Not run against a live compiled kernel, same sandbox limitation noted throughout this review; both handlers are read directly from source at the reviewed commit, and `IsForbiddenAbsPath`'s scope, plus the sync-root confirmation via `getSyncIgnoreLines()`, are quoted directly above. Whether these specific files are captured by history/repo snapshots in a given deployment depends on the workspace's actual usage history and was not independently verified against a live instance in this review.)* ### Impact An admin-authenticated request to either route can potentially retrieve a historical copy of `data/.siyuan/publishAccess.json` (disclosing a plaintext publish-mode password even after it has been changed or the live file has been protected) or a `data/templates/*` file, directly undermining the protection `GHSA-c8r8-95hg-mp34` was written four days prior to this review specifically to provide, via two routes that predate that fix and were not updated alongside it. ``` ## Affected products | Field | Value | |---|---| | Ecosystem | **Go** | | Package name | `github.com/siyuan-note/siyuan/kernel` | | Affected versions | Present as of commit `251596f` (2026-08-12, the version this review confirmed), i.e. postdates and was not covered by the `GHSA-c8r8-95hg-mp34` fix (commit `3542530`, 2026-08-08) | | Patched versions | *(none yet, leave blank until a fix is released)* | ## Severity | Field | Value | |---|---| | Vector string | `CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:M/I:N/A:N` | | Score | **~5.9 (Medium)**, `PR:H` since admin authentication is required at the HTTP layer, confidentiality impact scoped to whatever sensitive historical content happens to exist in the tracked snapshots for a given deployment (a real but deployment-dependent condition, honestly reflected as Medium rather than assumed to always be present), no integrity/availability impact since both are read-only. | ## Weaknesses (CWE) - **CWE-862**: Missing Authorization (primary) - **CWE-200**: Exposure of Sensitive Information to an Unauthorized Actor ## Notes for filing - Direct, narrow follow-up to `GHSA-c8r8-95hg-mp34`; recommend referencing that advisory directly when filing, since this is precisely the "sibling caller missed" pattern that fix's own centralization (moving the check into a shared `util` function) was presumably intended to prevent, just for two callers that existed before the shared function did and weren't migrated to it. - Suggested fix: add `if util.IsForbiddenAbsPath(p) { ... reject ... }` to both handlers, matching the pattern already applied in `kernel/api/file.go` and `kernel/mcp/tools/file.go`.

Exploitation Scenario

An attacker who has obtained or phished the workspace admin accessAuthCode, or a malicious insider with admin rights, sends authenticated GET requests to /history/<snapshot>/.siyuan/publishAccess.json or to /repo/diff/ for a historical checkout. The server returns the old plaintext publish password even though the live file is now blocked by IsForbiddenAbsPath(). The attacker uses the recovered password to access protected published notebooks, which may hold sensitive internal documentation or AI knowledge-base content. Credential reuse across other systems can widen the impact.

Weaknesses (CWE)

CWE-200 — Exposure of Sensitive Information to an Unauthorized Actor: The product exposes sensitive information to an actor that is not explicitly authorized to have access to that information.

  • [Architecture and Design] Compartmentalize the system to have "safe" areas where trust boundaries can be unambiguously drawn. Do not allow sensitive data to go outside of the trust boundary and always be careful when interfacing with a compartment outside of the safe area. Ensure that appropriate compartmentalization is built into the system design, and the compartmentalization allows for and reinforces privilege separation functionality. Architects and designers should rely on the principle of least privilege to decide the appropriate time to use privileges and the time to drop privileges.

Source: MITRE CWE corpus.

CVSS Vector

CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:N

Timeline

Published
October 5, 2026
Last Modified
October 5, 2026
First Seen
October 5, 2026

Related Vulnerabilities