---
id: CVE-2026-73606
aliases:
  - GHSA-vg99-7gj7-2fr5
title: >-
  SiYuan: The reference filter for getRefIDs checks visibility but not the
  password tier, disclosing that password-protected documents reference a given
  block
summary: >-
  SiYuan: The reference filter for getRefIDs checks visibility but not the
  password tier, disclosing that password-protected documents reference a given
  block
severity: medium
cvss: 5.8
cwe:
  - CWE-863
vendor: siyuan-note
product: github.com/siyuan-note/siyuan/kernel
ecosystem: go
affected:
  - github.com/siyuan-note/siyuan/kernel < 0.0.0-20260812083335-251596fc0de2
patched:
  - github.com/siyuan-note/siyuan/kernel 0.0.0-20260812083335-251596fc0de2
published: '2026-10-01'
updated: '2026-10-01'
sourceUpdated: '2026-10-01T16:29:36Z'
source: GHSA
sourceUrl: 'https://github.com/advisories/GHSA-vg99-7gj7-2fr5'
references:
  - url: >-
      https://github.com/siyuan-note/siyuan/security/advisories/GHSA-vg99-7gj7-2fr5
  - url: 'https://nvd.nist.gov/vuln/detail/CVE-2026-73606'
  - url: 'https://github.com/siyuan-note/siyuan/releases/tag/v3.8.0'
  - url: >-
      https://www.vulncheck.com/advisories/siyuan-before-information-disclosure-via-getrefids
  - url: 'https://github.com/advisories/GHSA-vg99-7gj7-2fr5'
tags:
  - ghsa
  - go
epss: 0.00325
epssPercentile: 0.23198
ingestedAt: '2026-10-01T18:55:42.381Z'
---

## Overview

### 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/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:

```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/publish_access.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 packages

- `github.com/siyuan-note/siyuan/kernel < 0.0.0-20260812083335-251596fc0de2`

## Remediation

Upgrade to a patched release:

- `github.com/siyuan-note/siyuan/kernel 0.0.0-20260812083335-251596fc0de2`
