CVE-2026-69084 — kernel
Fix: siyuan-note/siyuan@0015cbaCVE-2026-69084 is a SQL Injection vulnerability in github.com/siyuan-note/siyuan/kernel. A fix is available for github.com/siyuan-note/siyuan/kernel — see the affected versions and patch details below.
SiYuan before v3.7.3 SQL Injection via searchEmbedBlock
Exploitation Status
Proof-of-concept exploit code exists
- CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
- CISA assesses this as automatable — exploitation doesn’t require manual, per-target effort, which raises the odds of mass scanning and opportunistic attacks.
- A successful exploit gives an attacker total control of the affected component, not partial access.
Exploitation and automatability from CISA’s SSVC triage for CVE-2026-69084.
EPSS Exploitation Probability
Probability of exploitation in the next 30 days, from FIRST.org EPSS.
Real-World Exposure
github.com/siyuan-note/siyuan/kernelReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects Go packages — download data is not available via public APIs for these ecosystems.
Description
CVE: This vulnerability corresponds to CVE-2026-69084.
Summary
The /api/search/searchEmbedBlock endpoint passes a client-supplied SQL statement verbatim to the database with no validation. The endpoint is gated by CheckAuth only reachable by the publish RoleReader token, and by the anonymous account when Publish.Auth.Enable is false. The statement runs on the main read-write siyuan.db handle through a driver that executes stacked statements, with no single-statement or read-only guard. An unauthenticated request can therefore execute arbitrary SQL reading and writing content across all cleartext notebooks.
Unlike SQL injection into a fixed query, this endpoint accepts a full SQL statement by design and simply fails to restrict who may call it or what the statement may do.
Details
Data flow verbatim, unvalidated:
searchEmbedBlock(kernel/api/search.go):stmt := arg["stmt"].(string)passed directly tomodel.SearchEmbedBlock(stmt, …). No validation.SearchEmbedBlock→SearchEmbedBlockInBox→sql.SelectBlocksRawStmtNoParse(stmt, …)→selectBlocksRawStmt→query(stmt).query()(kernel/sql/database.go) callsdb.Query(stmt)on the globalsiyuan.dbhandle.
Missing guards. The comparable endpoints enforce restrictions this one omits:
/api/query/sqlrunsCheckSingleStatement(all modes) andCheckReadonlyStatement(readonly mode) and is route-gatedCheckAuth + CheckAdminRole + CheckReadonly.fullTextSearchBlockrejects the SQL search method for non-admins (if method == 2 && !IsAdminRoleContext(c)).
searchEmbedBlock has none of these, no statement check, no read-only check, no admin gate.
Route/auth tier. router.go: Handle("POST", "/api/search/searchEmbedBlock", model.CheckAuth, searchEmbedBlock), CheckAuth only. 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.
Handle/stacking. query() uses the global siyuan.db handle, the same read-write handle behind the accepted searchDocs finding. Driver is the vendored 88250/go-sqlite3 (mattn fork), whose connection query loops over ;-separated statements, so stacked statements execute for their side effects. The DSN sets no mode=ro / _query_only, so the handle is read-write; ATTACH is available.
Post-hoc filter does not bound the statement. FilterEmbedBlocksByPublishAccess runs on the returned slice after query() has executed. It filters rows; it cannot constrain what the statement did. Any write or ATTACH side effect has already occurred before the filter runs. It is not a security boundary for this sink.
Scope. The main blocks/DB handle spans every opened cleartext notebook. Encrypted notebooks use separate per-box databases and are excluded.
Impact
An unauthenticated request (publish mode with auth disabled) or any publish RoleReader executes arbitrary SQL on the main read-write database. This permits cross-notebook read disclosure of document content, and via the read-write handle and statement stacking, modification of database content and ATTACH-reachable files. No admin role, CSRF token, or write permission through the normal API is required. Encrypted notebooks are not exposed. Code execution is not reachable in the default build (no load_extension).
PoC Steps
- Build the kernel image from pinned HEAD
docker build -f D:/bb/zitadel/chatto/siyuan/Dockerfile.poc -t siyuan-head D:/bb/zitadel/chatto/siyuan
- Run fresh, workspace mounted to the host
docker rm -f siyuan-poc 2>nul
docker run -d --name siyuan-poc -p 6806:6806 -p 6808:6808 -v D:/bb/siyuan:/siyuan/workspace siyuan-head serve --accessAuthCode=1234567 --port=6806
Confirm it booted (version string back, not Cobra help):
docker logs siyuan-poc
curl -s http://127.0.0.1:6806/api/system/version
-
Grab the admin API token from the host file. Use that value wherever TOKEN appears below (yours was g4wj3r04ntobe9m4).
-
Turn on the reader surface: publish on 6808, Basic Auth OFF
curl -s -X POST http://127.0.0.1:6806/api/setting/setPublish -H "Content-Type: application/json" -H "Authorization: Token TOKEN" -d "{\"enable\":true,\"port\":6808,\"auth\":{\"enable\":false,\"accounts\":[]}}"
Must return "data":{"port":6808,...} (non-zero port = bound OK). Port 6808, not 6806.
- THE PROOF: anonymous reader on 6808, no token (the differential)
Guarded sibling rejects reader SQL:
curl -i -X POST http://127.0.0.1:6808/api/search/fullTextSearchBlock -H "Content-Type: application/json" -d "{\"query\":\"SELECT * FROM blocks LIMIT 1\",\"method\":2}"
→ -1 / "SQL search requires administrator privileges"
searchEmbedBlock accepts the same reader SQL (no guard):
curl -i -X POST http://127.0.0.1:6808/api/search/searchEmbedBlock -H "Content-Type: application/json" -d "{\"embedBlockID\":\"\",\"stmt\":\"SELECT * FROM blocks LIMIT 1\",\"excludeIDs\":[]}"
→ HTTP 200 / code:0 : arbitrary SQL executed at the anonymous reader tier. This pair is the finding.
Suggested fix
Apply the same controls the sibling SQL endpoints already use: route searchEmbedBlock through CheckSingleStatement and CheckReadonlyStatement, and gate the raw-SQL capability behind CheckAdminRole (as /api/query/sql and fullTextSearchBlock's SQL method do). At minimum, the read paths should run on a _query_only=1 handle so a reader-reachable statement cannot write or ATTACH.
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 🐹Go | github.com/siyuan-note/siyuan/kernel | all versions | 0.0.0-20260721002947-23a17d44b5f3go get github.com/siyuan-note/siyuan/kernel@v0.0.0-20260721002947-23a17d44b5f3 |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for github.com/siyuan-note/siyuan/kernel, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
Fix
Update github.com/siyuan-note/siyuan/kernel to 0.0.0-20260721002947-23a17d44b5f3 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-69084 is resolved across your whole dependency graph.
Workarounds
Until you can upgrade, make sure every query built from user input uses parameterised statements or a prepared-statement API rather than string concatenation, and reduce the database account's privileges so an injected query cannot read or alter data beyond what the feature needs.
How to detect CVE-2026-69084
A community-maintained Nuclei template exists for this CVE. You can scan for it directly:
nuclei -id cve-2026-69084 -u https://target- Template
- SiYuan - SQL Execution
- Severity
- critical
- Impact
- Attackers can read and modify content across all opened cleartext notebooks, potentially compromising data integrity and confidentiality.
- Remediation
- Update to version v3.7.3 or later.
Template by ProjectDiscovery nuclei-templates (Boreas37), MIT licensed. View the full template. Scan only systems you are authorised to test.
Frequently Asked Questions
Is CVE-2026-69084 in your dependencies?
Find it across Go, including transitive dependencies.