CVE-2026-73609: SiYuan GetBookmarkLabels Returns Every Bookmark Label Vulnerability

MODERATE
Advisory Identifier: GHSA-j4ph-9xwf-wcj4
CVSS 5.8
CWE-862
Published
Advisory Summary

CVE-2026-73609 is a MODERATE (CVSS 5.8) getbookmarklabels returns every bookmark label vulnerability (CWE-862: Missing Authorization) affecting SiYuan libraries in the Go ecosystem. The issue involves getbookmarklabels returns every bookmark label in the workspace to anonymous readers, with no publish-access filtering. Remediation is available by upgrading affected packages to version 0.0.0-20260812083335-251596fc0de2.

CVE-2026-73609 at a Glance

CVE Identifier CVE-2026-73609
GHSA Identifier GHSA-j4ph-9xwf-wcj4
Severity MODERATE
CVSS v3.1 Base Score 5.8
CWE Classification CWE-862: Missing Authorization
Affected Product SiYuan
Package Ecosystem Go
Published Date
Last Updated

Security Impact

CVSS: 5.8

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:N/A:N).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required None (PR:N)
User Interaction None (UI:N)
Scope Changed (S:C)
Confidentiality Low (C:L)
Integrity None (I:N)
Availability None (A:N)

How to Fix CVE-2026-73609

To remediate this vulnerability, upgrade all affected SiYuan dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

github.com/siyuan-note/siyuan/kernel fixed: 0.0.0-20260812083335-251596fc0de2
Upgrade github.com/siyuan-note/siyuan/kernel to 0.0.0-20260812083335-251596fc0de2
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for github.com/siyuan-note/siyuan/kernel

How to Check if You Are Affected

To determine whether your application is affected by CVE-2026-73609, inspect your project dependencies for SiYuan:

inspect dependencies for github.com/siyuan-note/siyuan/kernel

Analyze the terminal output to locate any version of SiYuan in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 0.0.0-20260812083335-251596fc0de2), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-862: Missing Authorization, where the product does not perform an authorization check when an actor attempts to access a resource or perform an action. In SiYuan, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

### Summary `/api/attr/getBookmarkLabels` is registered with `CheckAuth` only and applies no filtering of any kind. It runs a scan of the entire `blocks` table and returns the distinct set of every bookmark label in the workspace. An anonymous reader in publish mode receives the author's complete bookmark vocabulary, regardless of whether the bookmarked blocks live in published, hidden, password-protected or forbidden documents. The adjacent endpoint that returns bookmarks with their blocks does filter, and does so in a way that makes the intended rule explicit: it drops a label entirely when no accessible block carries it. ### Details **Route.** `kernel/api/router.go:297` on master, `:300` on the development branch: ```go ginServer.Handle("POST", "/api/attr/getBookmarkLabels", model.CheckAuth, getBookmarkLabels) ``` No `CheckAdminRole`, no `CheckReadonly`. Reachable by the publish `RoleReader` token and anonymously when `Publish.Auth.Enable` is `false`. **The handler** (`kernel/api/attr.go`) is a single line: ```go ret.Data = model.BookmarkLabels() ``` which reaches `kernel/model/bookmark.go:184` and then `sql.QueryBookmarkLabels()` at `kernel/sql/block_query.go:315`: ```go sqlStmt := "SELECT * FROM blocks WHERE ial LIKE ?" // "%bookmark=%" // collect distinct ialAttr(block.IAL, "bookmark") into a set, sort, return ``` There is no notebook scoping, no path scoping, no publish-access check and no filter function anywhere on the path. Every block in the workspace carrying a bookmark attribute contributes its label to the result. Bookmark labels are user-authored organisational text. They routinely name the thing being organised, which is the reason they are useful and also the reason they disclose. **The guarded sibling states the rule outright.** `getBookmark` (`kernel/api/router.go:190` on master, `:193` on the development branch) carries the same `CheckAuth`-only exposure and handles the same data. It does this: ```go bookmark.Blocks = model.FilterBlocksByPublishAccess(c, publishAccess, bookmark.Blocks) bookmark.Count = len(bookmark.Blocks) if bookmark.Count > 0 { *tempBookmarks = append(*tempBookmarks, bookmark) } ``` The final condition is the significant part. The label is not merely returned with an empty block list, it is omitted from the response entirely when no accessible block carries it. The project has therefore already decided that a reader must not learn a bookmark label they have no accessible block for. `getBookmarkLabels` returns those same labels unconditionally, one endpoint away. **Relationship to the tag-label fix.** Commit `4515fa257` ("Enforce publish access for tag labels") established that user-authored organisational labels are data the publish boundary must protect. Bookmark labels are the same class of data, drawn from the same IAL attribute store, reached through a structurally similar handler. That commit touched `kernel/api/tag.go`, `kernel/model/publish_access.go` and its test file, and nothing in `attr.go` or the bookmark path, so this endpoint was not covered by it. Worth noting that the defect here is broader than the one in the tag path. The tag filter existed and checked the visible tier, so it correctly excluded forbidden documents and leaked only the password tier. `getBookmarkLabels` has no filter at all, so labels from forbidden documents are returned as well. ### Proof of Concept Precondition: publish mode enabled (default port 6808), anonymous when `Publish.Auth.Enable` is `false`, otherwise any publish reader account. At least one bookmarked block in a document that is not published. ``` POST http://127.0.0.1:6808/api/attr/getBookmarkLabels {} → 200, a sorted array of every distinct bookmark label in the workspace, including labels used only inside documents the reader has no publish access to ``` No arguments are required. For contrast, the sibling endpoint returns nothing for those same labels to the same session: ``` POST http://127.0.0.1:6808/api/attr/getBookmark {} → 200, labels omitted entirely where no accessible block carries them ``` One endpoint withholds the label, the other returns it. ### Impact An anonymous reader in publish mode, or any publish `RoleReader`, obtains the author's complete bookmark vocabulary across the workspace. Bookmark labels are free text chosen to describe what is being organised, so the set discloses subject matter, project names, personal and organisational names, and the general shape of what the author is working on, including material in documents that are hidden, password-protected or explicitly forbidden to readers. Scoped honestly: this returns labels only. No block identifiers, document identifiers or content are included, so it does not extend into the identifier-to-content resolution paths. Confidentiality only, with no integrity or availability impact. ### Suggested fix Mirror `getBookmark`. For read-only roles, build the label set from blocks that have passed `FilterBlocksByPublishAccess`, and emit a label only if at least one surviving block carries it. That is the rule the sibling endpoint already implements, applied to the same data.

CVE-2026-73609 Affected SiYuan Versions

1 component(s)
Fixed Versions:
0.0.0-20260812083335-251596fc0de2
Product / Package Ecosystem Affected Versions Fixed Version
github.com/siyuan-note/siyuan/kernel
Go
< 0.0.0-20260812083335-251596fc0de2
0.0.0-20260812083335-251596fc0de2

References

Frequently Asked Questions

What is CVE-2026-73609?
CVE-2026-73609 is a security vulnerability in SiYuan involving getBookmarkLabels returns every bookmark label in the workspace to anonymous readers, with no publish-access filtering under CWE-862: Missing Authorization.
Which SiYuan versions are affected?
Affected versions include releases prior to 0.0.0-20260812083335-251596fc0de2 across affected packages: github.com/siyuan-note/siyuan/kernel.
What version fixes CVE-2026-73609?
Upgrading to 0.0.0-20260812083335-251596fc0de2 (or the latest patched release for your respective branch) resolves the vulnerability.
Is github.com/siyuan-note/siyuan/kernel affected?
Yes, github.com/siyuan-note/siyuan/kernel is affected in releases prior to 0.0.0-20260812083335-251596fc0de2. Upgrade your dependency to 0.0.0-20260812083335-251596fc0de2 or later.
What is the CVSS score of CVE-2026-73609?
CVE-2026-73609 has a CVSS v3.1 base score of 5.8 (MODERATE) with vector string CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:N/A:N.
What CWE is associated with CVE-2026-73609?
CVE-2026-73609 is classified under 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 can I check Go dependencies for CVE-2026-73609?
Run the dependency tree command: 'inspect dependencies for github.com/siyuan-note/siyuan/kernel'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-j4ph-9xwf-wcj4",
  "modified" : "2026-10-01T16:23:37Z",
  "published" : "2026-10-01T16:23:37Z",
  "aliases" : [ "CVE-2026-73609" ],
  "summary" : "SiYuan: getBookmarkLabels returns every bookmark label in the workspace to anonymous readers, with no publish-access filtering",
  "details" : "### Summary\n\n`/api/attr/getBookmarkLabels` is registered with `CheckAuth` only and applies no filtering of any kind. It runs a scan of the entire `blocks` table and returns the distinct set of every bookmark label in the workspace. An anonymous reader in publish mode receives the author's complete bookmark vocabulary, regardless of whether the bookmarked blocks live in published, hidden, password-protected or forbidden documents.\n\nThe adjacent endpoint that returns bookmarks with their blocks does filter, and does so in a way that makes the intended rule explicit: it drops a label entirely when no accessible block carries it.\n\n### Details\n\n**Route.** `kernel/api/router.go:297` on master, `:300` on the development branch:\n\n```go\nginServer.Handle(\"POST\", \"/api/attr/getBookmarkLabels\", model.CheckAuth, getBookmarkLabels)\n```\n\nNo `CheckAdminRole`, no `CheckReadonly`. Reachable by the publish `RoleReader` token and anonymously when `Publish.Auth.Enable` is `false`.\n\n**The handler** (`kernel/api/attr.go`) is a single line:\n\n```go\nret.Data = model.BookmarkLabels()\n```\n\nwhich reaches `kernel/model/bookmark.go:184` and then `sql.QueryBookmarkLabels()` at `kernel/sql/block_query.go:315`:\n\n```go\nsqlStmt := \"SELECT * FROM blocks WHERE ial LIKE ?\"   // \"%bookmark=%\"\n// collect distinct ialAttr(block.IAL, \"bookmark\") into a set, sort, return\n```\n\nThere is no notebook scoping, no path scoping, no publish-access check and no filter function anywhere on the path. Every block in the workspace carrying a bookmark attribute contributes its label to the result.\n\nBookmark labels are user-authored organisational text. They routinely name the thing being organised, which is the reason they are useful and also the reason they disclose.\n\n**The guarded sibling states the rule outright.** `getBookmark` (`kernel/api/router.go:190` on master, `:193` on the development branch) carries the same `CheckAuth`-only exposure and handles the same data. It does this:\n\n```go\nbookmark.Blocks = model.FilterBlocksByPublishAccess(c, publishAccess, bookmark.Blocks)\nbookmark.Count  = len(bookmark.Blocks)\nif bookmark.Count > 0 {\n    *tempBookmarks = append(*tempBookmarks, bookmark)\n}\n```\n\nThe final condition is the significant part. The label is not merely returned with an empty block list, it is omitted from the response entirely when no accessible block carries it. The project has therefore already decided that a reader must not learn a bookmark label they have no accessible block for. `getBookmarkLabels` returns those same labels unconditionally, one endpoint away.\n\n**Relationship to the tag-label fix.** Commit `4515fa257` (\"Enforce publish access for tag labels\") established that user-authored organisational labels are data the publish boundary must protect. Bookmark labels are the same class of data, drawn from the same IAL attribute store, reached through a structurally similar handler. That commit touched `kernel/api/tag.go`, `kernel/model/publish_access.go` and its test file, and nothing in `attr.go` or the bookmark path, so this endpoint was not covered by it.\n\nWorth noting that the defect here is broader than the one in the tag path. The tag filter existed and checked the visible tier, so it correctly excluded forbidden documents and leaked only the password tier. `getBookmarkLabels` has no filter at all, so labels from forbidden documents are returned as well.\n\n### Proof of Concept\n\nPrecondition: publish mode enabled (default port 6808), anonymous when `Publish.Auth.Enable` is `false`, otherwise any publish reader account. At least one bookmarked block in a document that is not published.\n\n```\nPOST http://127.0.0.1:6808/api/attr/getBookmarkLabels\n{}\n\n→ 200, a sorted array of every distinct bookmark label in the workspace,\n       including labels used only inside documents the reader has no\n       publish access to\n```\n\nNo arguments are required. For contrast, the sibling endpoint returns nothing for those same labels to the same session:\n\n```\nPOST http://127.0.0.1:6808/api/attr/getBookmark\n{}\n\n→ 200, labels omitted entirely where no accessible block carries them\n```\n\nOne endpoint withholds the label, the other returns it.\n\n### Impact\n\nAn anonymous reader in publish mode, or any publish `RoleReader`, obtains the author's complete bookmark vocabulary across the workspace. Bookmark labels are free text chosen to describe what is being organised, so the set discloses subject matter, project names, personal and organisational names, and the general shape of what the author is working on, including material in documents that are hidden, password-protected or explicitly forbidden to readers.\n\nScoped honestly: this returns labels only. No block identifiers, document identifiers or content are included, so it does not extend into the identifier-to-content resolution paths. Confidentiality only, with no integrity or availability impact.\n\n### Suggested fix\n\nMirror `getBookmark`. For read-only roles, build the label set from blocks that have passed `FilterBlocksByPublishAccess`, and emit a label only if at least one surviving block carries it. That is the rule the sibling endpoint already implements, applied to the same data.",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:N/A:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Go",
      "name" : "github.com/siyuan-note/siyuan/kernel"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "0.0.0-20260812083335-251596fc0de2"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/siyuan-note/siyuan/security/advisories/GHSA-j4ph-9xwf-wcj4"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-73609"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/siyuan-note/siyuan"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/siyuan-note/siyuan/releases/tag/v3.8.0"
  }, {
    "type" : "WEB",
    "url" : "https://www.vulncheck.com/advisories/siyuan-before-information-disclosure-via-getbookmarklabels"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-862" ],
    "severity" : "MODERATE",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-10-01T16:23:37Z",
    "nvd_published_at" : null
  }
}