GHSA-v684-q882-jgmq CVE-2026-72789 GO-2026-6434
SiYuan: The publish-access gate treats encrypted notebooks as publicly accessible by default, allowing anonymous readers to retrieve fully decrypted document content while a notebook is unlocked
- Severity
- 9.2 / 10
- Critical · CVSS 4.0
- Exploit chance (EPSS)
- 0.37%
- Chance of exploitation in the next 30 days. Higher than 30.77% of scored vulnerabilities.
- Known exploited
- No
- Not in CISA's catalog
- Weakness
- CWE-862 Missing Authorization
- Fix
0.0.0-20260726020813-a25c2dd06aae- Upgrade github.com/siyuan-note/siyuan/kernel to this version or later
Description
CVE: This vulnerability corresponds to CVE-2026-72789.
Summary
publishAccess.json is an opt-out list. The publish gate returns accessible for anything not explicitly listed in it. Encrypted notebooks are never written into that file, because only the administrator-gated setPublishAccess writes it and no part of the encryption subsystem does. Consequently every encrypted notebook is publish-accessible as far as the gate is concerned.
While an encrypted notebook is unlocked, an anonymous reader in publish mode can list it, enumerate its documents, and retrieve their fully decrypted content. No key material, no password, and no cracking is involved. The kernel decrypts the data and serves it because the authorization layer never asks whether the notebook is encrypted.
This is reported as a defect in the gate rather than in any individual handler. Endpoints that were previously reviewed and found to apply the correct checks do apply them. The checks return true.
Details
The gate defaults to accessible. CheckPathAccessableByPublishIgnore(box, path, ignore) iterates the ignore list and returns false only on a match:
for _, item := range publishIgnore {
if item.ID == box || strings.Contains(path, item.ID) {
return false
}
}
return true // unlisted means accessible
Encrypted notebooks are never listed. publishAccess.json is written only by setPublishAccess (kernel/api/router.go:169), which carries CheckAdminRole and CheckReadonly. Nothing in crypto.go, encrypted_ops.go or notebook_crypto.go writes to it. An administrator would have to manually add each encrypted notebook to the ignore list to protect it, and nothing in the product prompts or documents that.
Neither core gate rejects them. On both eef105683 and v3.7.4-alpha.1:
func checkBlockTreeAccessableByPublishAccess(...) bool {
return CheckPathAccessableByPublishIgnore(bt.BoxID, bt.Path, publishIgnore) &&
(password == "" || CheckPublishAuthCookie(c, passwordID, password))
}
For an encrypted notebook the first term is true because it is unlisted, and the second is true because encrypted notebooks carry no publish password. The conjunction returns true.
IsEncryptedBox does not appear in kernel/model/publish_access.go at all on master. On the development branch it appears once, inside parseAttributeViewForPublishAccess, where it routes into encrypted boxes rather than excluding them. The publish authorization layer has no concept of an encrypted notebook.
The chain. Verified on the development branch, which is the harder target because the block-metadata, attribute-view, path-resolution and encrypted-notebook-status fixes have all landed there.
lsNotebookscallsListNotebooks(), which includes encrypted notebooks and reportsEncrypted: boxConf.Encryptedwithout filtering on it. The reader filter skips only notebooks that areClosedor publish-invisible. An unlocked encrypted notebook is neither, so it is returned to an anonymous reader together with its identifier.listDocsByPath{notebook: <encrypted box id>, path: "/"}applies onlyCheckPathAccessableByPublishIgnore, which passes, returning the document identifiers and titles inside the encrypted notebook.getDocroutes toGetDocInBox(...), after whichFilterContentByPublishAccess(...)returns the content unmodified because the box is unlisted. The response is the fully decrypted document.getBlockKramdownreachesGetBlockKramdownInBoxfor the same result in Markdown.
Handlers reach the decrypted store through encryptedNotebookFromArg(arg) on a client-supplied notebook argument. This function predates the current release and is not a recent addition.
The precondition is the unlock window. GetBlockTreeInBox returns nil while a notebook is locked, treating it as nonexistent, and non-nil once unlocked. So the exposure is bounded to the period during which the legitimate user has the notebook open, which is precisely the period in which they are working in it. No action by the attacker triggers or extends that window, but no unusual condition is required either.
On endpoints previously assessed as correctly gated. getBlockDOM and getBlockKramdown apply password and visibility checks. exportPreview applies FilterContentByPublishAccess. Those assessments are accurate and this report does not contradict them. The checks execute and return true, because the visibility term resolves to accessible for an unlisted box and the password term is vacuous for a notebook that has no publish password. Patching any individual handler would not change that result.
Proof of Concept
Precondition: publish mode enabled (default port 6808), anonymous when Publish.Auth.Enable is false, otherwise any publish reader account. An encrypted notebook that is currently unlocked by the legitimate user.
Step 1, obtain the encrypted notebook's identifier:
POST http://127.0.0.1:6808/api/notebook/lsNotebooks
{}
→ 200. The list includes the encrypted notebook, with encrypted: true and its id.
Step 2, enumerate its documents:
POST http://127.0.0.1:6808/api/filetree/listDocsByPath
{"notebook":"<encrypted box id>","path":"/"}
→ 200, document identifiers and titles from inside the encrypted notebook
Step 3, retrieve decrypted content:
POST http://127.0.0.1:6808/api/filetree/getDoc
{"id":"<document id from step 2>"}
→ 200, the fully decrypted document
getBlockKramdown returns the same content as decrypted Markdown. Repeating step 3 while the notebook is locked returns not-found, which confirms the unlock window is the only thing standing between an anonymous reader and the plaintext.
Impact
The confidentiality guarantee of the encrypted-notebook feature is defeated against a remote, unauthenticated attacker for as long as the notebook is unlocked. A user who encrypts a notebook is expressing that its contents should be protected beyond the ordinary publish boundary. The publish gate does not recognise that intent, and instead treats the notebook as publicly readable because nobody added it to an opt-out list that the encryption feature does not write to.
The exposure is full document content rather than metadata, and it applies to every document in the notebook. It requires no key material, no password, no offline work and no interaction with the encryption subsystem at all.
Suggested fix
Fail closed on encryption, independently of publishAccess.json:
if IsEncryptedBox(bt.BoxID) {
return false
}
in both checkBlockTreeAccessableByPublishAccess and CheckBlockTreeMetadataAccessableByPublishAccess, and exclude encrypted notebooks from lsNotebooks and listDocsByPath for read-only roles.
The broader point is the default. An opt-out authorization list means every future notebook type, storage backend or content class is publicly accessible until somebody remembers to add it. Encrypted notebooks are the case where that default is most clearly wrong, but they are unlikely to be the only one.
Affected versions
Go
| Package | Vulnerable | Fixed in |
|---|---|---|
| github.com/siyuan-note/siyuan/kernel | < 0.0.0-20260726020813-a25c2dd06aae |
0.0.0-20260726020813-a25c2dd06aae |
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-20260726020813-a25c2dd06aae
- go
go get github.com/siyuan-note/siyuan/kernel@v0.0.0-20260726020813-a25c2dd06aae && go mod tidy
Severity breakdown
CVSS 4.0 9.2
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:H/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)
- High
- Integrity (vulnerable system)
- None
- Availability (vulnerable system)
- None
- Confidentiality (subsequent systems)
- High
- Integrity (subsequent systems)
- None
- Availability (subsequent systems)
- None
- Exploit maturity
- Not defined
- Scope
- X
CVSS 3.1 8.6
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N
- Attack vector
- Network
- Attack complexity
- Low
- Privileges required
- None
- User interaction
- None
- Scope
- Changed
- Confidentiality
- High
- Integrity
- None
- Availability
- None
Weakness
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
Advisories
Packages
Other links
Timeline
- Published
- Last updated by the source
- Latest EPSS score
Related advisories
| Advisory | Severity | Why 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 |
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