Skip to content

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

Critical Go
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.

  1. lsNotebooks calls ListNotebooks(), which includes encrypted notebooks and reports Encrypted: boxConf.Encrypted without filtering on it. The reader filter skips only notebooks that are Closed or publish-invisible. An unlocked encrypted notebook is neither, so it is returned to an anonymous reader together with its identifier.
  2. listDocsByPath{notebook: <encrypted box id>, path: "/"} applies only CheckPathAccessableByPublishIgnore, which passes, returning the document identifiers and titles inside the encrypted notebook.
  3. getDoc routes to GetDocInBox(...), after which FilterContentByPublishAccess(...) returns the content unmodified because the box is unlisted. The response is the fully decrypted document. getBlockKramdown reaches GetBlockKramdownInBox for 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

PackageVulnerableFixed 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

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