Skip to content

GHSA-4pjg-x8qj-33j5 CVE-2026-73630

SiYuan: authFilePublishAccess distinguishes hidden and forbidden documents from public and nonexistent ones, and issues a publish-auth cookie for documents it reports as forbidden

Medium Go
Severity
6.9 / 10
Medium · CVSS 4.0
Exploit chance (EPSS)
0.34%
Chance of exploitation in the next 30 days. Higher than 27.84% of scored vulnerabilities.
Known exploited
No
Not in CISA's catalog
Weakness
CWE-203 Observable Discrepancy
Fix
0.0.0-20260731093152-c7522b8db586
Upgrade github.com/siyuan-note/siyuan/kernel to this version or later

Description

Summary

/api/filetree/authFilePublishAccess is registered with CheckAuth only and is reachable anonymously. It never sets a failure code, so its outcome is signalled entirely by the response message and by whether a Set-Cookie header is present. Those two signals differ across access tiers, which lets an anonymous caller determine, for any candidate document identifier, whether that document is public or nonexistent, password-protected, or exists at the hidden or forbidden tier.

Separately, hidden and forbidden entries carry an empty password. Submitting password:"" therefore satisfies the equality check for them, and the server responds by issuing Set-Cookie: publish-auth-<docID> for a document whose own tier is forbidden.

Details

Route, identical on both refs:

ginServer.Handle("POST", "/api/filetree/authFilePublishAccess", model.CheckAuth, authFilePublishAccess)

No CheckAdminRole, no CheckReadonly.

Handler, kernel/api/filetree.go:1608 on the development branch and :1421 on master. ret.Code is never assigned on the failure path, so every response carries code:0. The branch is:

if item.Password == password {
    // SetPublishAuthCookie
} else {
    ret.Msg = Conf.Language(285)
}

The cookie is issued for forbidden documents. Entries at the hidden and forbidden tiers store Password == "". A request with password:"" therefore makes item.Password == password true for them, and the handler takes the success branch. The server issues a publish-auth cookie for a document it otherwise treats as inaccessible.

The cookie itself confers nothing, because those tiers are decided before any password check elsewhere in the codebase. The defect is that the endpoint's success signal does not correspond to access being granted, and that signal is observable.

Three distinguishable outcomes. With password:"", one request per candidate identifier:

Tier of the target Set-Cookie Body
Public (no publish-access entry) absent {"code":0,"msg":""}
Nonexistent identifier absent {"code":0,"msg":""}
Password-protected absent {"code":0,"msg":"Password is incorrect"}
Hidden present {"code":0,"msg":""}
Forbidden present {"code":0,"msg":""}

Public and nonexistent are indistinguishable from each other, which is correct. Everything else separates. A caller learns whether a given identifier names a password-protected document, or a document that exists at the hidden or forbidden tier, which is precisely the class the publish tiers exist to conceal.

Identifiers are readily available. Document and block identifiers appear in the DOM of published pages, in reference and backlink payloads, and in annotation reference data. The caller does not need to guess them.

A fourth response on the development branch. IsEncryptedPublishRuntimeTarget sets ret.Code = -1, which identifies the supplied identifier as belonging to an encrypted notebook. That is a membership oracle for encrypted notebooks, the same data class as GHSA-f2rw-w22v-54vh.

Proof of Concept

Publish mode enabled, Publish.Auth.Enable false, anonymous client. One request per candidate identifier, all with an empty password:

POST /api/filetree/authFilePublishAccess
{"id":"<candidate document id>","password":""}

Comparing the response message and the presence of Set-Cookie against the table above classifies the identifier. Documents at the hidden and forbidden tiers return a publish-auth cookie and an empty message, distinguishing them both from public documents and from identifiers that name nothing at all.

Impact

An anonymous reader in publish mode, or any publish RoleReader, can classify arbitrary document identifiers by access tier and confirm the existence of documents at the hidden and forbidden tiers. On the development branch the same endpoint additionally reveals whether an identifier belongs to an encrypted notebook.

Scoped precisely: no document content is disclosed and no privilege is gained, since the issued cookie is inert for these tiers. The value is as a confirmation primitive, turning an identifier obtained elsewhere into a definite statement about a document the reader is not permitted to see.

Confidentiality only, with no integrity or availability impact.

Suggested fix

Return an identical response for every failure and for every tier the caller may not access, with no Set-Cookie header and no distinguishing message. In particular, do not treat an empty stored password as a successful match: hidden and forbidden entries should short-circuit before the password comparison rather than satisfying it. The encrypted-notebook branch should return the same generic response as everything else rather than its own code.

A rate limit on this endpoint would be a reasonable additional hardening measure, though it does not address the disclosure above.

Affected versions

Go

PackageVulnerableFixed in
github.com/siyuan-note/siyuan/kernel
< 0.0.0-20260731093152-c7522b8db586
0.0.0-20260731093152-c7522b8db586

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-20260731093152-c7522b8db586

go
go get github.com/siyuan-note/siyuan/kernel@v0.0.0-20260731093152-c7522b8db586 && go mod tidy

Severity breakdown

CVSS 4.0 6.9

CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:L/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
None
User interaction
None
Confidentiality (vulnerable system)
Low
Integrity (vulnerable system)
None
Availability (vulnerable system)
None
Confidentiality (subsequent systems)
Low
Integrity (subsequent systems)
None
Availability (subsequent systems)
None
Exploit maturity
Not defined
Scope
X

CVSS 3.1 5.8

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

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

Weakness

CWE-203: Observable Discrepancy

The product behaves differently or sends different responses under different circumstances in a way that is observable to an unauthorized actor.

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.
  • Implementation: Ensure that error messages only contain minimal details that are useful to the intended audience and no one else. The messages need to strike the balance between being too cryptic (which can confuse users) or being too detailed (which may reveal more than intended). The messages should not reveal the methods that were used to determine the error. Attackers can use detailed information to refine or optimize their original attack, thereby increasing their chances of success. If errors must be captured in some detail, record them in log messages, but consider what could occur if the log messages can be viewed by attackers. Highly sensitive information such as passwords should never be saved to log files. Avoid inconsistent messaging that might accidentally tip off an attacker about internal state, such as whether a user account exists or not.

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-3cm4-ccvw-6xr6
SiYuan: /history/*path and /repo/diff/*path potentially exposing historical snapshots of data/.siyuan/publishAccess.json and data/templates/*
Medium 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
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