CVE-2026-87808 Overview
CVE-2026-87808 is a read-only boundary bypass in SiYuan, an open-source personal knowledge management application. Versions at or below 3.8.1 contain an incomplete fix for the prior issue CVE-2026-32767. The POST /api/search/fullTextSearchBlock endpoint accepts caller-supplied SQL when method=2 but fails to enforce the workspace read-only boundary. An authenticated administrator can submit arbitrary SQL and gain raw read access to the blocks database, even when the workspace runs with --readonly=true. The dedicated /api/query/sql endpoint is properly blocked in read-only mode, making this a direct bypass of the intended access boundary. The issue is fixed in SiYuan v3.8.2.
Critical Impact
Authenticated administrators can bypass the SiYuan read-only workspace boundary and execute arbitrary SQL against the blocks database via /api/search/fullTextSearchBlock.
Affected Products
- SiYuan versions <= 3.8.1
- SiYuan workspaces launched with --readonly=true
- Fixed in SiYuan v3.8.2
Discovery Timeline
- 2026-09-09 - CVE-2026-87808 published to NVD
- 2026-09-09 - Last updated in NVD database
Technical Details for CVE-2026-87808
Vulnerability Analysis
SiYuan exposes two SQL-capable HTTP endpoints. The /api/query/sql endpoint enforces read-only mode through calls to model.CheckReadonly and model.CheckReadonlyStatementInBox. The /api/search/fullTextSearchBlock endpoint accepts a method parameter, and when method=2 it forwards the caller-supplied query directly to the blocks database as SQL.
The earlier fix in commit d5e2d0bc for CVE-2026-32767 added an administrator role check for method=2. That check restricts who can call the SQL path but does not evaluate the workspace read-only state. As a result, an administrator retains an unrestricted SQL read path against the blocks database while the workspace is running with --readonly=true.
This is a broken access control weakness classified under [CWE-693] (Protection Mechanism Failure). The vulnerability enables information disclosure of any content stored in the blocks database, including note bodies, metadata, and internal identifiers that the read-only policy is designed to gate.
Root Cause
The root cause is inconsistent enforcement of the read-only boundary across SQL-capable endpoints. /api/query/sql invokes the read-only checks, while /api/search/fullTextSearchBlock with method=2 reaches model.FullTextSearchBlock without invoking model.CheckReadonly or model.CheckReadonlyStatementInBox.
Attack Vector
An authenticated administrator sends a POST request to /api/search/fullTextSearchBlock with method=2 and a query field containing arbitrary SQL. The backend executes the statement against the blocks database and returns matching rows, bypassing the read-only workspace policy that would otherwise block equivalent requests to /api/query/sql.
}
page, pageSize, query, paths, boxes, types, method, orderBy, groupBy := parseSearchBlockArgs(arg)
// SQL mode requires admin privileges, consistent with /api/query/sql
if method == 2 && !model.IsAdminRoleContext(c) {
ret.Code = -1
ret.Msg = "SQL search requires administrator privileges"
return
}
blocks, matchedBlockCount, matchedRootCount, pageCount, docMode := model.FullTextSearchBlock(query, boxes, paths, types, method, orderBy, groupBy, page, pageSize)
if model.IsReadOnlyRoleContext(c) {
publishAccess := model.GetPublishAccess()
Source: SiYuan commit d5e2d0bc. This is the prior partial patch that added the administrator check but did not add read-only enforcement. The complete fix ships in v3.8.2.
Detection Methods for CVE-2026-87808
Indicators of Compromise
- HTTP POST requests to /api/search/fullTextSearchBlock where the JSON body contains "method": 2.
- Request bodies whose query field contains SQL keywords such as SELECT, FROM blocks, UNION, or PRAGMA rather than typical search text.
- Administrator sessions issuing SQL-shaped fullTextSearchBlock calls while the workspace was launched with --readonly=true.
Detection Strategies
- Inspect application access logs for /api/search/fullTextSearchBlock calls and flag payloads where method=2 originates from any client outside expected admin tooling.
- Correlate SiYuan startup flags with request patterns: any method=2 SQL search that succeeds while --readonly=true is set indicates exploitation of this boundary bypass.
- Alert on repeated SQL-mode search requests from a single session, which may indicate enumeration of the blocks database.
Monitoring Recommendations
- Enable verbose HTTP logging on the SiYuan kernel and forward it to a central log store for retention and searching.
- Monitor administrator account creation and role changes in SiYuan, since exploitation requires an authenticated admin context.
- Track the deployed SiYuan version across hosts to confirm all instances are running v3.8.2 or later.
How to Mitigate CVE-2026-87808
Immediate Actions Required
- Upgrade all SiYuan instances to v3.8.2 or later, which enforces the read-only boundary on /api/search/fullTextSearchBlock when method=2.
- Audit existing administrator accounts and revoke any that are not strictly required for operations.
- Rotate SiYuan authentication tokens and session credentials after upgrading if exposure is suspected.
Patch Information
The issue is fixed in SiYuan v3.8.2. Details are published in GitHub Security Advisory GHSA-4qwm-3p58-vh67 and the VulnCheck advisory for SiYuan. The prior partial fix is tracked in SiYuan commit d5e2d0bc.
Workarounds
- Restrict network access to the SiYuan kernel HTTP interface so only trusted clients can reach /api/search/fullTextSearchBlock.
- Place a reverse proxy in front of SiYuan and block POST requests to /api/search/fullTextSearchBlock whose JSON body contains "method":2 until the upgrade is applied.
- Avoid running SiYuan with administrator sessions active in environments where the read-only boundary is a required control.
# Example NGINX rule to block SQL-mode fullTextSearchBlock requests
location = /api/search/fullTextSearchBlock {
if ($request_method = POST) {
access_by_lua_block {
ngx.req.read_body()
local body = ngx.req.get_body_data() or ""
if body:find('"method"%s*:%s*2') then
ngx.exit(403)
end
}
}
proxy_pass http://siyuan_upstream;
}
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

