{"repro_id":"REPRO-2026-00305","version":6,"title":"SiYuan missing authorization in /mcp enables unauthenticated administrator takeover via Publish proxy","repro_type":"security","status":"published","severity":"critical","description":"Repository: https://github.com/siyuan-note/siyuan Immutable vulnerable ref: c72ca4cd09019e5f64afdee8f8c6ec5ef34858db (fix commit) Immutable fixed ref: v3.7.2 Affected versions: before 3.7.2 Fixed versions: 3.7.2 and later Attacker control: remote unauthenticated attacker controls HTTP requests reaching the Publish proxy and then the /mcp endpoint; attacker can also control JSON/tool arguments sent to MCP tools. Public entrypoint: Publish server enabled in anonymous mode, then POST /mcp via the reverse proxy. Expected impact: unauthorized read/write of workspace files, secret exfiltration from conf/conf.json, plugin planting, and administrator takeover on next desktop launch. Primary references: https://github.com/siyuan-note/siyuan/security/advisories/GHSA-cvhv-7xhj-xjp8 ; https://github.com/siyuan-note/siyuan/commit/c72ca4cd09019e5f64afdee8f8c6ec5ef34858db ; https://www.vulncheck.com/advisories/siyuan-before-unauthenticated-administrator-takeover-via-mcp Negative control: without Publish anonymous exposure or when Publish auth is enabled, the unauthenticated attacker cannot reach the vulnerable /mcp path. Generic runtime reproduction objective: in a Linux Docker sandbox, run vulnerable SiYuan with Publish anonymous mode enabled, issue a request through the Publish proxy to /mcp, and verify unauthorized file read/write capability versus v3.7.2.","root_cause":"# Root Cause Analysis: CVE-2026-66012\n\n## Summary\n\nSiYuan versions before 3.7.2 expose the management-grade MCP endpoint through the Publish reverse proxy when Publish is enabled in anonymous mode. An unauthenticated remote HTTP client can POST to `/mcp` on the Publish port, obtain an MCP session, and invoke the built-in `file` tool. That tool can read and write workspace files, including `conf/conf.json` and plugin configuration under `data/plugins` and `data/storage/petal`. In this run the unauthenticated MCP write primitive is chained into code execution by planting a desktop plugin in the workspace, restarting/launching the real SiYuan Electron desktop runtime under Xvfb, and capturing attacker-controlled `id` command output from the plugin's renderer-side JavaScript.\n\n## Impact\n\n- **Package/component affected:** SiYuan kernel MCP service, Publish reverse proxy, workspace file tooling, and desktop plugin loader.\n- **Affected versions:** Before 3.7.2. The reproduction uses vulnerable v3.7.1 and fixed v3.7.2.\n- **Risk level and consequences:** Critical. With Publish anonymous mode enabled and reachable, an unauthenticated remote attacker can use `/mcp` to read workspace files, exfiltrate `conf/conf.json`, write arbitrary workspace files, plant plugins, and obtain command execution when the real desktop/Electron runtime next loads the planted plugin. Publish authentication enabled or v3.7.2 blocks the unauthenticated `/mcp` path.\n\n## Impact Parity\n\n- **Disclosed/claimed maximum impact:** Unauthenticated administrator takeover / code execution through Publish-proxied `/mcp`, with workspace file read/write, secret exfiltration, plugin planting, and execution on next desktop launch.\n- **Reproduced impact from this run:** Full unauthenticated HTTP access to `/mcp` through the Publish reverse proxy on v3.7.1; unauthorized file write/read; `conf/conf.json` exfiltration; plugin files and `petals.json` planted using the same unauthenticated `/mcp` session; real SiYuan Electron desktop launched under Xvfb; planted plugin executes attacker-controlled JavaScript with Node/Electron `child_process` access and writes a marker containing `id` output. Fixed v3.7.2 and Publish-auth-enabled controls reject the initial unauthenticated `/mcp` request and therefore block the planting path.\n- **Parity:** `full`.\n- **Not demonstrated:** Interactive administrator UI takeover is not separately demonstrated because the reproduced attacker-controlled command execution is a stronger concrete runtime effect than file-only takeover. The command executes as the SiYuan desktop user inside the tested Linux sandbox.\n\n## Root Cause\n\nBefore v3.7.2, the `/mcp` route was reachable after the Publish reverse proxy applied the anonymous Publish access context. The MCP server exposed tools that are equivalent to local/admin workspace management primitives, including arbitrary workspace file read/write. In anonymous Publish mode, the reverse proxy allowed unauthenticated clients to reach `/mcp`; the MCP handler accepted `initialize` and subsequent `tools/call` requests without enforcing an administrator role for this management surface. Because the `file` tool accepts attacker-controlled paths inside the workspace, an unauthenticated client can write plugin files under `data/plugins/<name>/` and enable the plugin via `data/storage/petal/petals.json`.\n\nThe desktop plugin loader later loads enabled plugins from those workspace files. In the desktop renderer, SiYuan v3.7.1 creates BrowserWindows with `nodeIntegration: true` and `contextIsolation: false`; plugin JavaScript is evaluated via the plugin loader and can call Node/Electron modules. Therefore, a planted plugin can call `require(\"child_process\")` and execute a local command when the real desktop application starts with that workspace.\n\nThe v3.7.2 fix adds authorization requirements to MCP. In the fixed source, the MCP routes are registered with `model.CheckAuth`, `model.CheckAdminRole`, and `model.CheckReadonly`, so Publish anonymous access no longer satisfies the admin requirement for `/mcp`. Public references include the advisory and fixed release/commit listed in the ticket: https://github.com/siyuan-note/siyuan/security/advisories/GHSA-cvhv-7xhj-xjp8 and SiYuan v3.7.2.\n\n## Reproduction Steps\n\n1. Run `bundle/repro/reproduction_steps.sh`.\n2. The script reuses or creates the SiYuan checkout in the prepared project cache, builds the real v3.7.1 and v3.7.2 kernels, ensures the real v3.7.1 Linux desktop release is available, starts the vulnerable v3.7.1 kernel with Publish anonymous mode enabled, and sends HTTP requests to `POST /mcp` through the real Publish reverse proxy at `127.0.0.1:6808`.\n3. The script obtains an unauthenticated MCP session, writes and reads `pwned.txt`, exfiltrates `conf/conf.json`, plants a desktop plugin and `petals.json` via the MCP file tool, stops the kernel, and launches the real SiYuan Electron desktop under Xvfb with the same workspace. The plugin writes a marker containing `id` output to `bundle/logs/repro/desktop-rce-marker.log`.\n4. The script also runs negative controls: v3.7.1 with Publish auth enabled rejects unauthenticated `/mcp`, and v3.7.2 with anonymous Publish rejects the initial `/mcp` request before any plugin planting can occur.\n\nExpected evidence is a successful script exit, vulnerable `/mcp` response logs containing `file written`, `owned by unauthenticated mcp`, and `kernelVersion`, the desktop marker containing `PRUVA_MCP_DESKTOP_RCE` plus `uid=...`, and fixed/auth-control logs showing HTTP 401/403 style rejection of unauthenticated `/mcp`.\n\n## Evidence\n\nThe reproduction script writes all runtime-backed evidence under `bundle/logs/repro/` and records the proof artifacts in `bundle/repro/runtime_manifest.json`. Key artifacts include:\n\n- `bundle/logs/repro/mcp-init-response.log` — unauthenticated `POST /mcp initialize` through the Publish reverse proxy returns HTTP 200 and `Mcp-Session-Id` on v3.7.1.\n- `bundle/logs/repro/mcp-write-response.log` — MCP `file` tool writes `pwned.txt` as an unauthenticated attacker.\n- `bundle/logs/repro/mcp-read-response.log` — MCP `file` tool reads back the attacker-controlled content.\n- `bundle/logs/repro/mcp-conf-response.log` — MCP `file` tool reads `conf/conf.json` from the workspace.\n- `bundle/logs/repro/mcp-plugin-json-write-response.log`, `bundle/logs/repro/mcp-plugin-index-write-response.log`, and `bundle/logs/repro/mcp-petals-write-response.log` — plugin planting performed through the same unauthenticated MCP session.\n- `bundle/logs/repro/siyuan-desktop-rce.log` and `bundle/logs/repro/siyuan-desktop-rce-app.log` — real Electron desktop launch under Xvfb with the planted workspace.\n- `bundle/logs/repro/desktop-rce-marker.log` — marker generated by attacker-planted plugin code, containing `PRUVA_MCP_DESKTOP_RCE` and `id` output.\n- `bundle/logs/repro/siyuan-vuln-auth.log` and `bundle/logs/repro/mcp-auth-control-response.log` — Publish authentication enabled blocks unauthenticated `/mcp`.\n- `bundle/logs/repro/siyuan-fixed-anon.log` and `bundle/logs/repro/mcp-fixed-control-response.log` — fixed v3.7.2 blocks the same unauthenticated `/mcp` request even with anonymous Publish enabled.\n\nEnvironment details captured by the script include source tag/commit resolution, kernel build logs, real desktop release extraction, and runtime logs from the vulnerable and fixed kernels.\n\n## Recommendations / Next Steps\n\n- Upgrade SiYuan to v3.7.2 or later.\n- Keep Publish authentication enabled whenever Publish is exposed, and do not expose anonymous Publish to untrusted networks on affected versions.\n- Treat `/mcp` and similar file/plugin/workspace management routes as local/admin-only surfaces. Require an authenticated administrator role before dispatching MCP methods or tools.\n- Add regression tests that exercise `/mcp` through the Publish reverse proxy with anonymous Publish enabled, Publish auth enabled, and authenticated-admin contexts. The anonymous and non-admin cases must fail before MCP session creation and tool dispatch.\n- Consider defense in depth for plugin loading: explicit trust prompts, safer defaults for `bazaar.trust`, and stronger separation between remote content exposure and local plugin execution.\n\n## Additional Notes\n\n- The reproduction is idempotent: it uses fresh `/tmp` workspaces and marker paths, kills leftover SiYuan kernel/desktop processes before and after each attempt, and can be run repeatedly.\n- The desktop code-execution proof requires Linux GUI dependencies and Xvfb because it launches the real Electron product path. The script installs required packages when absent and uses the real upstream SiYuan v3.7.1 Linux desktop tarball.\n- The code-execution marker is generated only after the unauthenticated `/mcp` write primitive plants the plugin in the current run; it is not a pre-planted artifact.\n","cve_id":"CVE-2026-66012","cwe_id":"CWE-862 Missing Authorization","source_url":"https://github.com/siyuan-note/siyuan/security/advisories/GHSA-cvhv-7xhj-xjp8","package":{"name":"siyuan","ecosystem":"github","affected_versions":"before 3.7.2 (specifically 3.7.1 per advisory)"},"reproduced_at":"2026-07-27T06:32:53.418694+00:00","duration_secs":10317.0,"tool_calls":853,"handoffs":2,"total_cost_usd":57.51733746,"agent_costs":{"judge":3.192591,"learning_policy":0.009919,"repro":46.58667457,"support":0.05298889,"vuln_variant":7.675164},"cost_breakdown":{"judge":{"gpt-5.5":3.192591},"learning_policy":{"gpt-5.4-mini-2026-03-17":0.009919},"repro":{"accounts/fireworks/models/kimi-k2p7-code":4.63459257,"gpt-5.5":41.952082},"support":{"accounts/fireworks/models/kimi-k2p7-code":0.05298889},"vuln_variant":{"gpt-5.5":7.675164}},"quality":{"confidence":"high","idempotent_verified":false,"community_verifications":0},"evidence":{"workflow":{"profile":"known_vulnerability","schema_version":2,"stages":["support","claim_contract","repro","judge","vuln_variant"]}},"environment":{"sandbox_image":"ghcr.io/n3mes1s/pruva-sandbox@sha256:8096b2518d6022e13d68f885c3b8ded6b4fe607098b1a1ccbfb99abc004d1dc1"},"published_at":"2026-07-27T06:32:54.763089+00:00","retracted":false,"artifacts":[{"path":"bundle/repro/rca_report.md","filename":"rca_report.md","size":8889,"category":"analysis"},{"path":"bundle/repro/reproduction_steps.sh","filename":"reproduction_steps.sh","size":22136,"category":"reproduction_script"},{"path":"bundle/repro/exploit_knowledge.json","filename":"exploit_knowledge.json","size":18525,"category":"other"},{"path":"bundle/logs/repro/mcp-fixed-control-response.log","filename":"mcp-fixed-control-response.log","size":85,"category":"log"},{"path":"bundle/logs/repro/desktop-rce-marker-1785119719-59078.log","filename":"desktop-rce-marker-1785119719-59078.log","size":92,"category":"log"},{"path":"bundle/logs/repro/desktop-rce-marker-1785119775-59728.log","filename":"desktop-rce-marker-1785119775-59728.log","size":92,"category":"log"},{"path":"bundle/logs/repro/desktop-rce-marker-1785131179-1.log","filename":"desktop-rce-marker-1785131179-1.log","size":88,"category":"log"},{"path":"bundle/logs/repro/desktop-rce-marker.log","filename":"desktop-rce-marker.log","size":88,"category":"log"},{"path":"bundle/logs/repro/escalation_experiment.log","filename":"escalation_experiment.log","size":1516,"category":"log"},{"path":"bundle/logs/repro/escalation_experiment2.log","filename":"escalation_experiment2.log","size":1544,"category":"log"},{"path":"bundle/logs/repro/escalation-boot2.log","filename":"escalation-boot2.log","size":2751,"category":"log"},{"path":"bundle/logs/repro/escalation-plugin-tool-response.log","filename":"escalation-plugin-tool-response.log","size":160,"category":"log"},{"path":"bundle/logs/repro/escalation2-fetch-response.log","filename":"escalation2-fetch-response.log","size":170,"category":"log"},{"path":"bundle/logs/repro/mcp-auth-control-response.log","filename":"mcp-auth-control-response.log","size":164,"category":"log"},{"path":"bundle/logs/repro/mcp-conf-response.log","filename":"mcp-conf-response.log","size":12651,"category":"log"},{"path":"bundle/logs/repro/command-exec-negative-control.json","filename":"command-exec-negative-control.json","size":212,"category":"other"},{"path":"bundle/logs/repro/mcp-init-response.log","filename":"mcp-init-response.log","size":642,"category":"log"},{"path":"bundle/logs/repro/mcp-petals-write-response.log","filename":"mcp-petals-write-response.log","size":117,"category":"log"},{"path":"bundle/logs/repro/mcp-plugin-index-write-response.log","filename":"mcp-plugin-index-write-response.log","size":117,"category":"log"},{"path":"bundle/logs/repro/mcp-plugin-json-write-response.log","filename":"mcp-plugin-json-write-response.log","size":120,"category":"log"},{"path":"bundle/logs/repro/mcp-read-response.log","filename":"mcp-read-response.log","size":101,"category":"log"},{"path":"bundle/logs/repro/mcp-write-response.log","filename":"mcp-write-response.log","size":96,"category":"log"},{"path":"bundle/logs/repro/reproduction_steps.log","filename":"reproduction_steps.log","size":2051,"category":"log"},{"path":"bundle/logs/repro/siyuan-desktop-rce-app.log","filename":"siyuan-desktop-rce-app.log","size":1388,"category":"log"},{"path":"bundle/logs/repro/siyuan-desktop-rce.log","filename":"siyuan-desktop-rce.log","size":4938,"category":"log"},{"path":"bundle/logs/repro/siyuan-fixed-anon.log","filename":"siyuan-fixed-anon.log","size":2496,"category":"log"},{"path":"bundle/logs/repro/siyuan-vuln-anon.log","filename":"siyuan-vuln-anon.log","size":3595,"category":"log"},{"path":"bundle/logs/repro/siyuan-vuln-auth.log","filename":"siyuan-vuln-auth.log","size":3314,"category":"log"},{"path":"bundle/logs/repro/source-v3.7.1-mcp-server.go","filename":"source-v3.7.1-mcp-server.go","size":3464,"category":"other"},{"path":"bundle/logs/repro/source-v3.7.2-mcp-server.go","filename":"source-v3.7.2-mcp-server.go","size":3794,"category":"other"},{"path":"bundle/repro/runtime_manifest.json","filename":"runtime_manifest.json","size":1589,"category":"other"},{"path":"bundle/repro/validation_verdict.json","filename":"validation_verdict.json","size":857,"category":"other"}]}