GHSA-vg99-7gj7-2fr5

Suggest an improvement
Source
https://github.com/advisories/GHSA-vg99-7gj7-2fr5
Import Source
https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-vg99-7gj7-2fr5/GHSA-vg99-7gj7-2fr5.json
JSON Data
https://api.osv.dev/v1/vulns/GHSA-vg99-7gj7-2fr5
Aliases
  • CVE-2026-73606
Published
2026-10-01T16:29:35Z
Modified
2026-10-01T16:45:42Z
Severity
  • 5.8 (Medium) CVSS_V3 - CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:N/A:N CVSS Calculator
Summary
SiYuan: The reference filter for getRefIDs checks visibility but not the password tier, disclosing that password-protected documents reference a given block
Details

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

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:

publishIgnore := model.GetInvisiblePublishAccess(publishAccess)
refDefs, originalRefBlockIDs = model.FilterRefDefsByPublishIgnore(publishIgnore, refDefs)

FilterRefDefsByPublishIgnore (kernel/model/publish_access.go:1324) collects the reference and definition identifiers, resolves their block trees, and delegates the decision to FilterBlockTreesByPublishIgnore (:1314), whose entire body is:

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/publish_access.go:431:

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.

Database specific
{
    "cwe_ids":  [
        "CWE-863"
    ],
    "github_reviewed":  true,
    "github_reviewed_at":  "2026-10-01T16:29:35Z",
    "nvd_published_at":  null,
    "severity":  "MODERATE"
}
References

Affected packages

Go / github.com/siyuan-note/siyuan/kernel

Package

Name
github.com/siyuan-note/siyuan/kernel
View open source insights on deps.dev
Purl
pkg:golang/github.com/siyuan-note/siyuan/kernel

Affected ranges

Type
SEMVER
Events
Introduced
0 Unknown introduced version / All previous versions are affected
Fixed
0.0.0-20260812083335-251596fc0de2

Database specific

source
"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-vg99-7gj7-2fr5/GHSA-vg99-7gj7-2fr5.json"