GHSA-69mh-gvh4-8gp7
SiYuan: Full-content disclosure of publish-disabled documents via getHeading*Transaction endpoints (publish mode): reader-reachable rendered DOM with no publish-access check
Packages
| Ecosystem | Package | Affected | Fixed in |
|---|---|---|---|
| Go | github.com/siyuan-note/siyuan/kernel | < 0.0.0-20260721013353-69db783b782a | 0.0.0-20260721013353-69db783b782a |
Details
As written in the advisory (Markdown source, shown as plain text).
**CVE:** This vulnerability corresponds to [CVE-2026-68587](https://nvd.nist.gov/vuln/detail/CVE-2026-68587).
### Summary
Three "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`.
Despite 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.
### Details
**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.
**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.
**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.
### Proof of Concept
Reproduced 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.
**1. Confirm the document is publish-disabled (admin action, the boundary that should block reads):**
```
POST http://127.0.0.1:6806/api/filetree/setPublishAccess
Authorization: Token <admin-token>
{"id":"DOC","visible":false,"password":"","disable":true}
```
**2. Baseline: the reader-safe content path correctly blocks it (anonymous, port 6808):**
```
POST http://127.0.0.1:6808/api/filetree/getDoc
{"id":"DOC"}
```
Returns the blocked/placeholder response, the publish filter is applied, no content.
**3. Disclosure: the transaction endpoint returns the content (anonymous, port 6808):**
```
POST http://127.0.0.1:6808/api/block/getHeadingDeleteTransaction
{"id":"HEADING"}
```
Returns 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.
**4. Sibling endpoints: same disclosure, same input:**
```
POST http://127.0.0.1:6808/api/block/getHeadingLevelTransaction
{"id":"HEADING","level":2}
POST http://127.0.0.1:6808/api/block/getHeadingInsertTransaction
{"id":"HEADING"}
```
Both return the rendered DOM of the heading subtree in their operation data.
### Impact
An 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.
**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.
Impact 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.
### Suggested fix
Apply 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.
References
External links from the advisory. They lead to third-party sites.
- https://github.com/siyuan-note/siyuan/security/advisories/GHSA-69mh-gvh4-8gp7
- https://nvd.nist.gov/vuln/detail/CVE-2026-68587
- https://github.com/siyuan-note/siyuan/commit/69db783b782aea865ea70a1c5ab656e5c0f3dadb
- https://github.com/siyuan-note/siyuan
- https://www.vulncheck.com/advisories/siyuan-before-information-disclosure-via-getheading-transaction