Skip to content

GHSA-3cm4-ccvw-6xr6 CVE-2026-82651 GO-2026-6662

SiYuan: /history/*path and /repo/diff/*path potentially exposing historical snapshots of data/.siyuan/publishAccess.json and data/templates/*

Medium Go
Severity
6.9 / 10
Medium · CVSS 4.0
Exploit chance (EPSS)
0.48%
Chance of exploitation in the next 30 days. Higher than 41.24% of scored vulnerabilities.
Known exploited
No
Not in CISA's catalog
Weakness
CWE-200 Exposure of Sensitive Information to an Unauthorized Actor
CWE-862 Missing Authorization
Fix
0.0.0-20260816034002-035bf9a8c311
Upgrade github.com/siyuan-note/siyuan/kernel to this version or later

Description

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):

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):

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:

// 禁止访问 data/.siyuan/publishAccess.json(含发布模式明文访问密码)
publishAccessPath := NormalizeAndResolve(filepath.Join(DataDir, ".siyuan", "publishAccess.json"))
if fileNorm == publishAccessPath {
    return true
}

and

// 禁止访问 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:
    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`.

Affected versions

Go

PackageVulnerableFixed in
github.com/siyuan-note/siyuan/kernel
< 0.0.0-20260816034002-035bf9a8c311
0.0.0-20260816034002-035bf9a8c311

How to fix

Move to the fixed version or later with your package manager, update the lockfile, and run your tests. If the package only arrives through another dependency, upgrade that dependency first. If none of its releases pulls a fixed version yet, force the version with your package manager's override.

github.com/siyuan-note/siyuan/kernel Go → 0.0.0-20260816034002-035bf9a8c311

go
go get github.com/siyuan-note/siyuan/kernel@v0.0.0-20260816034002-035bf9a8c311 && go mod tidy

Severity breakdown

CVSS 4.0 6.9

CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Attack vector
Network
Attack complexity
Low
Attack requirements
None
Privileges required
High
User interaction
None
Confidentiality (vulnerable system)
High
Integrity (vulnerable system)
None
Availability (vulnerable system)
None
Confidentiality (subsequent systems)
None
Integrity (subsequent systems)
None
Availability (subsequent systems)
None
Exploit maturity
Not defined
Scope
X

CVSS 3.1 4.9

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

Attack vector
Network
Attack complexity
Low
Privileges required
High
User interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
None
Availability
None

Weakness

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.

How to prevent it
  • 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.

CWE-862: Missing Authorization

The product does not perform an authorization check when an actor attempts to access a resource or perform an action.

How to prevent it
  • Architecture and Design: Divide the product into anonymous, normal, privileged, and administrative areas. Reduce the attack surface by carefully mapping roles with data and functionality. Use role-based access control (RBAC) [REF-229] to enforce the roles at the appropriate boundaries. Note that this approach may not protect against horizontal authorization, i.e., it will not protect a user from attacking others with the same role.
  • Architecture and Design: Ensure that access control checks are performed related to the business logic. These checks may be different than the access control checks that are applied to more generic resources such as files, connections, processes, memory, and database records. For example, a database may restrict access for medical records to a specific database user, but each record might only be intended to be accessible to the patient and the patient's doctor [REF-7].
  • Architecture and Design: Use a vetted library or framework that does not allow this weakness to occur or provides constructs that make this weakness easier to avoid. For example, consider using authorization frameworks such as the JAAS Authorization Framework [REF-233] and the OWASP ESAPI Access Control feature [REF-45].
  • Architecture and Design: For web applications, make sure that the access control mechanism is enforced correctly at the server side on every page. Users should not be able to access any unauthorized functionality or information by simply requesting direct access to that page. One way to do this is to ensure that all pages containing sensitive information are not cached, and that all such pages restrict access to requests that are accompanied by an active and authenticated session token associated with a user who has the required permissions to access that page.
  • System Configuration, Installation: Use the access control capabilities of your operating system and server environment and define your access control lists accordingly. Use a "default deny" policy when defining these ACLs.

References

Timeline

  1. Published
  2. Last updated by the source
  3. Latest EPSS score
AdvisorySeverityWhy it's related
GHSA-xp3q-r38w-vgqm
SiYuan: The publish filter for local storage returns the administrator's entire storage map with only three keys sanitized
Medium Also affects github.com/siyuan-note/siyuan/kernel
GHSA-9cqf-hhrq-7v45
SiYuan: getAttributeViewSearchTarget returns database row content to anonymous readers with no publish-access check, reopening the class closed one day earlier at the adjacent route
High Also affects github.com/siyuan-note/siyuan/kernel
GHSA-57v5-wqx3-cgj4
SiYuan: Database view structure (all view names, layout types and per-field visibility) is returned to anonymous readers by /api/av/getAttributeViewFieldViews
Medium Also affects github.com/siyuan-note/siyuan/kernel
GHSA-4wwp-f6gw-6qm5
SiYuan: TLS Private Keys Readable via getFile (Incomplete Blocklist)
High Also affects github.com/siyuan-note/siyuan/kernel
GHSA-x8gv-g2g3-65fj
SiYuan Agent Tools SSRF via DNS-Rebinding TOCTOU (Bypass of CheckHostSSRF)
High Also affects github.com/siyuan-note/siyuan/kernel
GHSA-p23f-cm6q-2qp8
SiYuan MCP asset.upload Reads Arbitrary Absolute File Paths (Workspace Boundary Bypass)
Medium Also affects github.com/siyuan-note/siyuan/kernel
GHSA-rpx2-p6hp-x5gj
SiYuan: Missing consistency check between packageName and repoURL/repoHash in bazaar package install allows overwriting an existing, trusted plugin
Medium Also affects github.com/siyuan-note/siyuan/kernel
GHSA-c8r8-95hg-mp34
SiYuan: MCP file tool's blocklist is incomplete relative to the HTTP file API it claims to align with, exposing publish-mode passwords and other sensitive workspace files
Medium Also affects github.com/siyuan-note/siyuan/kernel
Is this in your code?

MergeHarbor checks every repository's dependencies against this database, ranks what matters by exploitability and reachability, routes it to the owning team and opens the fix pull request.

Check your repos with MergeHarbor