{"id":"CVE-2026-68587","aliases":["GHSA-69mh-gvh4-8gp7","GO-2026-6381"],"title":"SiYuan: Full-content disclosure of publish-disabled documents via getHeading*Transaction endpoints (publish mode): reader-reachable rende…","summary":"SiYuan: Full-content disclosure of publish-disabled documents via getHeading*Transaction endpoints (publish mode): reader-reachable rendered DOM with no publish-access check","severity":"high","cvss":8.6,"cvssVector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N","vendor":"siyuan-note","product":"github.com/siyuan-note/siyuan/kernel","ecosystem":"go","affected":["github.com/siyuan-note/siyuan/kernel < 0.0.0-20260721013353-69db783b782a"],"patched":["github.com/siyuan-note/siyuan/kernel 0.0.0-20260721013353-69db783b782a"],"published":"2026-09-03","updated":"2026-09-10","sourceUpdated":"2026-09-10T15:26:00.079898016Z","source":"OSV","sourceUrl":"https://osv.dev/vulnerability/GHSA-69mh-gvh4-8gp7","references":[{"url":"https://github.com/siyuan-note/siyuan/security/advisories/GHSA-69mh-gvh4-8gp7"},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-68587"},{"url":"https://github.com/siyuan-note/siyuan/commit/69db783b782aea865ea70a1c5ab656e5c0f3dadb"},{"url":"https://github.com/siyuan-note/siyuan"},{"url":"https://www.vulncheck.com/advisories/siyuan-before-information-disclosure-via-getheading-transaction"},{"url":"https://github.com/advisories/GHSA-69mh-gvh4-8gp7"}],"tags":["osv","go","ghsa"],"epss":0.00245,"epssPercentile":0.16048,"cwe":["CWE-862"],"ingestedAt":"2026-09-03T22:09:24.133Z","slug":"CVE-2026-68587","body":"## Overview\n\n**CVE:** This vulnerability corresponds to [CVE-2026-68587](https://nvd.nist.gov/vuln/detail/CVE-2026-68587).\n\n### Summary\n\nThree \"heading transaction\" endpoints `/api/block/getHeadingDeleteTransaction`, `/api/block/getHeadingLevelTransaction`, and `/api/block/getHeadingInsertTransaction` return the rendered block DOM of a heading and its subtree in the computed transaction payload, with no publish-access check. They are gated by `CheckAuth` only, so they are reachable by the publish `RoleReader` token, and by the anonymous account when `Publish.Auth.Enable` is `false`.\n\nDespite their write-implying names, these endpoints perform no mutation on this path, they compute a transaction object and return it, and that object contains `RenderNodeBlockDOM` output. As a result, an anonymous reader who supplies a heading block ID can read the full rendered content of a document that has been explicitly marked publish-disabled by an administrator, content that the reader-facing `/api/filetree/getDoc` path correctly refuses to return.\n\n### Details\n\n**Route / auth tier.** All three routes are registered `CheckAuth`-only (no `CheckAdminRole`). `CheckAuth` admits `RoleReader`, and the publish proxy forwards port-6808 traffic with a Reader JWT (anonymous account when publish auth is disabled). Anonymous/reader reachable.\n\n**No publish-access filter.** `GetHeadingDeleteTransaction` / `GetHeadingLevelTransaction` / `GetHeadingInsertTransaction` load the heading's block tree and render its DOM into the returned transaction's operation data. None of the three invokes `IsReadOnlyRoleContext` / the publish-access filter that the reader-safe content path (`getDoc`) applies. The reader-facing content endpoints (`getDoc`, and the `CheckAdminRole`-gated `getBlockDOM`/`getBlockKramdown`) treat rendered block DOM as gated content; these three transaction endpoints return the same rendered DOM without that gate.\n\n**Write-implying name, read behavior.** On this code path the endpoints only *compute* the transaction (e.g. the `undoOperations` a delete would produce) and return it; nothing is deleted or modified. The returned operation data includes the rendered HTML of the heading subtree. This mismatch, mutation-shaped name, content-returning behavior is why the gap is easy to miss.\n\n### Proof of Concept\n\nReproduced on a local instance (SiYuan running locally, publish mode enabled on port 6808, publish Basic Auth disabled). `DOC` is a document containing a heading `HEADING` whose body contains the unique marker `UNIQUE_MARKER_99`, and `DOC` is marked publish-disabled by the administrator.\n\n**1. Confirm the document is publish-disabled (admin action, the boundary that should block reads):**\n```\nPOST http://127.0.0.1:6806/api/filetree/setPublishAccess\nAuthorization: Token <admin-token>\n{\"id\":\"DOC\",\"visible\":false,\"password\":\"\",\"disable\":true}\n```\n\n**2. Baseline: the reader-safe content path correctly blocks it (anonymous, port 6808):**\n```\nPOST http://127.0.0.1:6808/api/filetree/getDoc\n{\"id\":\"DOC\"}\n```\nReturns the blocked/placeholder response, the publish filter is applied, no content.\n\n**3. Disclosure: the transaction endpoint returns the content (anonymous, port 6808):**\n```\nPOST http://127.0.0.1:6808/api/block/getHeadingDeleteTransaction\n{\"id\":\"HEADING\"}\n```\nReturns HTTP 200; `data.undoOperations[].data` contains the rendered HTML of the heading subtree, including the hidden body text `UNIQUE_MARKER_99`. Nothing is deleted — the endpoint only computes the transaction. This is the full content of a publish-disabled document returned to an anonymous reader.\n\n**4. Sibling endpoints: same disclosure, same input:**\n```\nPOST http://127.0.0.1:6808/api/block/getHeadingLevelTransaction\n{\"id\":\"HEADING\",\"level\":2}\n\nPOST http://127.0.0.1:6808/api/block/getHeadingInsertTransaction\n{\"id\":\"HEADING\"}\n```\nBoth return the rendered DOM of the heading subtree in their operation data.\n\n### Impact\n\nAn anonymous reader (publish mode with auth disabled) or any publish `RoleReader` who supplies a heading block ID can read the full rendered content of a publish-disabled document, defeating a boundary the administrator explicitly configured.\n\n**Precondition:** the request requires the target heading's block ID. Block IDs are high-entropy and are not returned by this endpoint, so this endpoint alone does not permit untargeted enumeration of arbitrary documents. However, block/heading IDs for publish-disabled documents are obtainable from other `CheckAuth`-only endpoints that lack the publish-access filter (the publish-boundary handler set reported separately, e.g. `getHeadingChildrenIDs`). Chained with such an ID source, this yields end-to-end unauthenticated full-content disclosure of publish-disabled documents. Presented standalone, the attacker must already possess the heading ID.\n\nImpact is confidentiality-only: these endpoints return content but do not (on this path) modify data. No admin role, CSRF token, or write permission is required. Encrypted notebooks are out of scope.\n\n### Suggested fix\n\nApply the same publish-access check the content path uses `IsReadOnlyRoleContext` / the publish-access filter used by `getDoc` to all three `getHeading*Transaction` handlers, or gate them behind `CheckAdminRole` consistent with `getBlockDOM`/`getBlockKramdown`. Any endpoint that returns rendered block DOM should enforce the same publish boundary as the primary content path.\n\n## Affected packages\n\n- `github.com/siyuan-note/siyuan/kernel < 0.0.0-20260721013353-69db783b782a`\n\n## Remediation\n\nUpgrade to a patched release:\n\n- `github.com/siyuan-note/siyuan/kernel 0.0.0-20260721013353-69db783b782a`","depth":"twilight","depthScore":47,"depthScoreParts":{"impact":47.3,"likelihood":0,"exploitation":0,"ransomware":0},"changes":[]}