{"id":"CVE-2026-68586","aliases":["GHSA-36v8-mpjm-8j5r","GO-2026-6379"],"title":"SiYuan: Cross-boundary content disclosure via getBacklinkDoc/getBackmentionDoc (publish mode): reader-reachable rendered DOM of publish-f…","summary":"SiYuan: Cross-boundary content disclosure via getBacklinkDoc/getBackmentionDoc (publish mode): reader-reachable rendered DOM of publish-forbidden docs; sibling list endpoints are filtered","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-20260721014413-f45749a7ef6e"],"patched":["github.com/siyuan-note/siyuan/kernel 0.0.0-20260721014413-f45749a7ef6e"],"published":"2026-09-03","updated":"2026-09-10","sourceUpdated":"2026-09-10T15:25:41.235385903Z","source":"OSV","sourceUrl":"https://osv.dev/vulnerability/GHSA-36v8-mpjm-8j5r","references":[{"url":"https://github.com/siyuan-note/siyuan/security/advisories/GHSA-36v8-mpjm-8j5r"},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-68586"},{"url":"https://github.com/siyuan-note/siyuan/commit/f45749a7ef6e385f6e2af6b7dd12429d56d46f32"},{"url":"https://github.com/siyuan-note/siyuan"},{"url":"https://www.vulncheck.com/advisories/siyuan-before-content-disclosure-via-getbacklinkdoc"},{"url":"https://github.com/advisories/GHSA-36v8-mpjm-8j5r"}],"tags":["osv","go","ghsa"],"epss":0.00241,"epssPercentile":0.15565,"cwe":["CWE-862"],"ingestedAt":"2026-09-03T22:09:24.090Z","slug":"CVE-2026-68586","body":"## Overview\n\n**CVE:** This vulnerability corresponds to [CVE-2026-68586](https://nvd.nist.gov/vuln/detail/CVE-2026-68586).\n\n### Summary\n\nThe backlink API splits into list endpoints (which documents reference a block) and content endpoints (the rendered text of those referencing blocks). The list endpoints apply a publish-access filter, the content endpoints do not. As a result, `/api/ref/getBacklinkDoc` and `/api/ref/getBackmentionDoc` return the rendered DOM of blocks belonging to a publish-forbidden document to an anonymous reader, with no access check.\n\nBoth content endpoints are gated by `CheckAuth` only, reachable by the publish `RoleReader` token and by the anonymous account when `Publish.Auth.Enable` is `false`.\n\n### Details\n\nThe asymmetry between the list and content sides is the tell that this is an oversight, not intended behavior:\n\n| Endpoint | Returns | Publish-access filter | Route |\n|---|---|---|---|\n| `getBacklink` | list (`*Path`) | `FilterPathsByPublishAccess` - present | `CheckAuth` |\n| `getBacklink2` | list (`*Path`) | `FilterPathsByPublishAccess` - present | `CheckAuth` |\n| `getBacklinkDoc` | rendered DOM | none | `CheckAuth` |\n| `getBackmentionDoc` | rendered DOM | none | `CheckAuth` |\n\n`model/backlink.go` contains no publish-access reference anywhere, and `Backlink.DOM` is the rendered HTML of the referencing blocks. `getBacklinkDoc(defID, refTreeID)` returns the rendered content of blocks in `refTreeID` - including a publish-forbidden, publish-disabled, or password-protected document with no access check. The filtered list siblings (`getBacklink`/`getBacklink2`) demonstrate that the publish boundary is meant to apply to this data; the content endpoints simply omit it.\n\nA reader is not limited to the filtered backlink list, they call `getBacklinkDoc` directly with any `refTreeID`. This also yields a reference-existence oracle: the response reveals whether the forbidden document `refTreeID` references the block `defID`.\n\n### Proof of Concept\n\nReproduced on a local instance (SiYuan running locally, publish mode enabled on port 6808, publish Basic Auth disabled). Setup: a publish-forbidden document `D` (`REFTREEID`) whose body contains the unique marker `SECRET_MARKER_77` and which references a block `DEFID` in a separate known document.\n\n**1. Mark the target document publish-forbidden (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\":\"REFTREEID\",\"visible\":false,\"password\":\"\",\"disable\":true}\n```\n\n**2. Baseline: the list endpoint correctly hides the forbidden doc from the reader (anonymous, port 6808):**\n```\nPOST http://127.0.0.1:6808/api/ref/getBacklink2\n{\"id\":\"DEFID\",\"k\":\"\",\"mk\":\"\"}\n```\nThe returned backlinks do not include the forbidden document `D`, the list side is filtered.\n\n**3. Disclosure: the content endpoint returns the forbidden doc's blocks anyway (anonymous, port 6808):**\n```\nPOST http://127.0.0.1:6808/api/ref/getBacklinkDoc\n{\"defID\":\"DEFID\",\"refTreeID\":\"REFTREEID\",\"keyword\":\"\"}\n```\nReturns HTTP 200; `data.backlinks[].dom` contains `SECRET_MARKER_77`, the rendered content of the publish-forbidden document, returned to an anonymous reader. `getBackmentionDoc` behaves identically for mention-type references.\n\n### Impact\n\nAn anonymous reader (publish mode with auth disabled) or any publish `RoleReader`, can read the rendered content of a publish-forbidden document's referencing blocks, defeating a boundary the administrator explicitly configured, and can determine whether a forbidden document references a given block (a reference-existence oracle).\n\n**Precondition (stated honestly):** the request requires `refTreeID` (the forbidden document's ID) and `defID` (a block it references). `defID` may be any published/known block, so if the forbidden document references any public content, `defID` is known and only `refTreeID` need be supplied. Block/document IDs for forbidden documents are also obtainable from other `CheckAuth`-only endpoints that lack the publish-access filter (reported separately). This endpoint alone does not enumerate arbitrary documents; it discloses content once an ID is known.\n\nImpact is confidentiality-only: content disclosure plus a reference-existence oracle, no modification. 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 list siblings use. In `getBacklinkDoc`/`getBackmentionDoc`, filter each `Backlink` by its source document's box/path via `CheckPathAccessableByPublishIgnore` plus the publish-password cookie check consistent with `FilterPathsByPublishAccess` in `getBacklink`/`getBacklink2`. Any endpoint returning rendered block DOM should enforce the same publish boundary as the corresponding list endpoint.\n\n## Affected packages\n\n- `github.com/siyuan-note/siyuan/kernel < 0.0.0-20260721014413-f45749a7ef6e`\n\n## Remediation\n\nUpgrade to a patched release:\n\n- `github.com/siyuan-note/siyuan/kernel 0.0.0-20260721014413-f45749a7ef6e`","depth":"twilight","depthScore":47,"depthScoreParts":{"impact":47.3,"likelihood":0,"exploitation":0,"ransomware":0},"changes":[]}