GHSA-vg99-7gj7-2fr5: Medium severity go/github.com/siyuan-note/siyuan/kernel vulnerability
Summary
/api/block/getRefIDs filters its results for reader roles through a helper that checks only the visibility tiers. The password tier is not checked, because the helper does not receive the request context and therefore cannot evaluate the publish auth cookie. A reader who has not entered a document's publish password learns that the document references a given block.
A function ten lines away in the same file does perform the full check, on the same input type.
Details
Route, identical at eef105683 (kernel/api/router.go:235) and dev a7ae96ce (:245):
go ginServer.Handle("POST", "/api/block/getRefIDs", model.CheckAuth, getRefIDs)
No CheckReadonly, no CheckAdminRole.
The filter chain. getRefIDs (kernel/api/block.go:631) checks isEncryptedNotebookDeniedForPublish, calls model.GetBlockRefsInBox, then for read-only roles:
go publishIgnore := model.GetInvisiblePublishAccess(publishAccess) refDefs, originalRefBlockIDs = model.FilterRefDefsByPublishIgnore(publishIgnore, refDefs)
FilterRefDefsByPublishIgnore (kernel/model/publishaccess.go:1324) collects the reference and definition identifiers, resolves their block trees, and delegates the decision to FilterBlockTreesByPublishIgnore (:1314), whose entire body is:
go for id, bt := range bts { if CheckPathAccessableByPublishIgnore(bt.BoxID, bt.Path, publishIgnore) { ret[id] = bt } }
Inside that helper, CheckPublishAuthCookie, GetPathPasswordByPublishAccess, checkBlockTreeAccessableByPublishAccess and password all appear zero times.
The complete check exists in the same file. kernel/model/publishaccess.go:431:
go func checkBlockTreeAccessableByPublishAccess(c gin.Context, publishAccess PublishAccess, bt treenode.BlockTree) bool { if bt == nil || IsEncryptedBoxDeniedByPublishAccess(bt.BoxID) { return false } publishIgnore := filterDisablePublishAccess(publishAccess) passwordID, password := GetPathPasswordByPublishAccess(bt.BoxID, bt.Path, publishAccess) return CheckPathAccessableByPublishIgnore(bt.BoxID, bt.Path, publishIgnore) && (password == "" || CheckPublishAuthCookie(c, passwordID, password)) }
Same package, same file, same treenode.BlockTree input. One takes the context and evaluates the password; the other does not receive it and structurally cannot.
Route-level contrast. The adjacent getChildBlocks and getTailChildBlocks both carry model.CheckAdminRole.
Note on a prior assessment. getRefIDs has been described as correctly filtered because it calls a publish-access filter. It does. The filter it calls covers the visibility tiers only.
Proof of Concept
Kernel 3.7.2, publish mode on port 6808, Publish.Auth.Enable false, anonymous client. A public document referenced from a second document whose publish tier was changed between runs. getRefIDs was called anonymously each time.
| Tier of the referring document | Anonymous result | |---|---| | Public | reference returned (baseline) | | Password-protected | reference still returned | | Hidden | [], filtered | | Forbidden | [], filtered |
The hidden and forbidden rows confirm the filter runs and works. The password-protected row is the defect.
Impact
An anonymous reader in publish mode, or any publish RoleReader, learns that a password-protected document contains a reference to a given block, without entering that document's password, and receives the block identifiers involved.
Scoped precisely: the response carries identifiers only. type RefDefs { RefID string; DefIDs []string }, returned alongside originalRefBlockIDs, a map of identifier to identifier. There is no reference text, title or content. The disclosure is the existence of a relationship, plus identifiers usable as input to other endpoints.
Confidentiality only.
Suggested fix
Thread gin.Context into FilterRefDefsByPublishIgnore and FilterBlockTreesByPublishIgnore, and use checkBlockTreeAccessableByPublishAccess in place of the bare CheckPathAccessableByPublishIgnore call, so the password tier and the encrypted-box check are both applied.
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
go/github.com/siyuan-note/siyuan/kernelto a version that resolves this vulnerability.Fixed in 0.0.0-20260812083335-251596fc0de2 - Compensating control
Fix /api/block/getRefIDs by threading *gin.Context through FilterRefDefsByPublishIgnore and FilterBlockTreesByPublishIgnore, and replacing the bare CheckPathAccessableByPublishIgnore call with checkBlockTreeAccessableByPublishAccess so the publish-password tier and encrypted-box check are applied.
Event History
Frequently Asked Questions
What access does an attacker need?
The issue affects reader-role users who have not entered a document's publish password. The affected handler is protected by model.CheckAuth, but it does not apply a read-only or admin-role check.
What information can be disclosed?
A reader who lacks the publish password can learn that a password-protected document references a particular block. The disclosure is limited to reference relationship information; no integrity or availability impact is described.