Back www.hunt-benito.com MCP as a Backdoor: CVE-2026-66012 — How a Missing Authorization Check in SiYuan's MCP Endpoint Turns Anonymous Readers into Administrators HB - Tailored Software Solutions / 1h The vulnerability, disclosed via GitHub Security Advisory GHSA-cvhv-7xhj-xjp8 on July 13, 2026, chains three independent defects in SiYuan’s kernel into an unauthenticated, network-reachable path to arbitrary workspace file read/write/delete, plaintext credential exfiltration, and remote code execution via plugin planting
On July 25, 2026, NIST’s National Vulnerability Database published CVE-2026-66012 , a missing-authorization vulnerability in SiYuan — the privacy-first, block-level note-taking application with over 45,000 GitHub stars and a large Docker-hosted user base. NVD scores it 10.0 Critical under CVSS 3.1 ( AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H ), the maximum possible score. The vulnerability, disclosed via GitHub Security Advisory GHSA-cvhv-7xhj-xjp8 on July 13, 2026, chains three independent defects in SiYuan’s kernel into an unauthenticated, network-reachable path to arbitrary workspace file read/write/delete , plaintext credential exfiltration, and remote code execution via plugin planting.
The attack surface at the heart of this vulnerability is one that the security community is only beginning to grapple with at scale: MCP (Model Context Protocol) . When Anthropic open-sourced MCP in late 2024, the pitch was compelling — a standardized JSON-RPC protocol that lets LLM-powered tools interact with local applications, files, databases, and APIs. SiYuan integrated MCP as a way to let AI assistants read and manage notes. But the integration exposed 31 tools — including a file tool with read , write , delete , rename , and copy primitives across the entire workspace — behind nothing more than a generic authentication check. No role enforcement. No admin gate.
This is the kind of bug that is going to become increasingly common as applications rush to bolt MCP servers onto their existing HTTP APIs. The protocol is powerful, the tool surface is wide, and the authorization model is often an afterthought. CVE-2026-66012 is a textbook example of what happens when new functionality inherits an old, insufficient authorization layer .
A note on the CVSS 4.0 vector from VulnCheck: it scores the maximum across every dimension — both the vulnerable system (SiYuan kernel) and the subsequent system (the desktop host via plugin RCE) see full Confidentiality, Integrity, and Availability impact. This is one of the rare CVEs where “10.0” is not a rounding artifact.
To understand why this vulnerability is so devastating, we need to map SiYuan’s internal architecture. SiYuan is built on a split model: a Go kernel that manages all data, serves an HTTP API, and runs the block-level database; and an Electron frontend (or a browser, when running headless via Docker) that communicates with the kernel exclusively over HTTP.
The kernel listens on two ports in a typical deployment:
The critical architectural detail is this: the Publish server is not a standalone content server . It is a reverse proxy that forwards requests to the kernel on port 6806. When a request arrives at the Publish port in anonymous mode, the proxy at kernel/server/proxy/publish.go intercepts it and injects an anonymous JWT — a RoleReader -scoped token — into the X-Auth-Token header before forwarding it upstream.
This design is the load-bearing wall. The Publish proxy’s intent is to give anonymous visitors read-only access to published content. But the kernel’s /mcp route does not enforce role checks beyond “is this a valid JWT?” So the reader-scoped token injected by the Publish proxy sails right through.
The kernel defines three roles in kernel/model/auth.go :
The CheckAuth middleware validates that the request carries a valid JWT. The CheckAdminRole middleware additionally verifies the caller is an Administrator. The CheckReadonly middleware blocks write operations for Reader-scoped tokens. In v3.7.1, the /mcp route had CheckAuth but neither of the other two.
MCP (Model Context Protocol) is a JSON-RPC 2.0 protocol introduced by Anthropic in November 2024. It standardizes how LLM-powered clients (Claude Desktop, IDE plugins, custom agents) discover and invoke tools exposed by a server. The protocol has three phases:
SiYuan registered its MCP server at POST /mcp in kernel/mcp/server.go . The registration was a single line of Go:
The 31 tools exposed via this endpoint included:
The file tool is where the damage concentrates. Its handler in kernel/mcp/tools/file.go maps directly to os.ReadFile , os.WriteFile , os.Remove , and os.Rename . The path resolution function resolvePath correctly prevents directory traversal outside the workspace via gulu.File.IsSubPath(util.WorkspaceDir, abs) — but every path inside the workspace is fully readable, writable, and deletable.
That includes conf/conf.json , the kernel’s plaintext configuration file.
The GHSA advisory meticulously traces three independent defects that compose into the unauthenticated attack chain. Each is a separate code issue, but together they form a kill chain.
File: kernel/mcp/server.go:29
The route is gated by model.CheckAuth , which only verifies that the JWT is valid. There is no model.CheckAdminRole and no model.CheckReadonly . This means any JWT that passes the basic authentication check — including a RoleReader token — can reach the MCP handler and invoke all 31 tools.
Compare this with how other admin-scoped endpoints are registered:
The MCP route was added without inheriting the middleware chain that every other admin-equivalent endpoint uses. The tool descriptions carry advisory text like “Read-only SQL on SiYuan’s database” and “debugging/log reading only, never use for workspace data” , but nothing in code enforces those claims.
File: kernel/mcp/handler.go:60-195
When a tools/call JSON-RPC method arrives, the handler dispatches it directly to the target tool’s handler function:
The dispatcher does not inspect the caller’s role. It does not check whether the tool being invoked is read-only. It does not enforce any per-tool authorization. Any tool is callable by any authenticated principal — including a Reader.
File: kernel/server/proxy/publish.go:229
When Publish.Auth.Enable=false (anonymous mode), the Publish reverse proxy unconditionally attaches an anonymous JWT to every forwarded request:
The anonymous account is bootstrapped in kernel/model/auth.go:91 as a RoleReader :
InitPublishJWT at line 105 issues a RoleReader -scoped JWT for every account in the map, including the empty-string anonymous one. That JWT is what the reverse proxy hands to /mcp when an unauthenticated visitor sends a request to the Publish port.
This is the link that converts the missing-authorization bug (Defect 1) into an unauthenticated vulnerability. Without the Publish proxy, an attacker would still need a valid Reader account. With it, no credentials are needed at all.
Now let’s walk through the full exploitation path. Every request targets the Publish port ( :6808 ) and sends no authentication headers .
The attacker completes the standard MCP initialization handshake. The server assigns a session ID that must be included in subsequent requests:
The response is HTTP/1.1 200 OK with a Mcp-Session-Id header. The handshake succeeds because the Publish proxy has already injected the anonymous JWT — the kernel sees a valid authentication token and processes the request normally.
The conf/conf.json file lives at WorkspaceDir/conf/conf.json and stores the kernel’s configuration in plaintext JSON. It contains three secrets that unlock the admin port:
The response’s .result.content[0].text field contains the full configuration file. The limit:-1 parameter bypasses the default 200-line truncation, returning every byte.
With the file tool’s write action, the attacker creates a plugin under data/plugins/ . SiYuan loads plugins on desktop startup by calling window.eval on each plugin’s index.js inside a BrowserWindow with dangerously permissive Electron settings:
These settings mean a planted plugin’s JavaScript executes with full Node.js APIs available, including require("child_process") . The attacker writes two files:
plugin.json (manifest required by the plugin loader):
index.js (payload executed on desktop launch):
On the desktop launch, the plugin loader at app/src/plugin/loader.ts:27-52 calls window.eval on this index.js , and the child_process.exec call runs with the desktop user’s operating system privileges.
Using the accessAuthCode stolen in Step 2, the attacker authenticates to the admin port directly:
The returned token grants full Administrator access. Alternatively, the attacker can use the stolen api.token directly as a bearer token on any admin API endpoint — no login ceremony required.
The PoC is a Python script that automates the full attack chain. Below is the core exploit logic:
Prerequisites: Docker 20.10+ and Python 3 with requests installed.
Lab setup (on a local machine — never test against a production instance ):
Enable the Publish server in anonymous mode:
Verify the planted plugin on the host mount:
When the workspace is opened in the SiYuan desktop app, the plugin’s onload fires and touch /tmp/siyuan-pwned executes with the user’s OS privileges.
Attention! The PoC targets a lab instance only. Running it against a production SiYuan deployment without explicit authorization is illegal. The accessAuthCode exfiltration and plugin planting steps are destructive to workspace integrity.
The impact chain crosses two trust boundaries :
Concretely, an unauthenticated attacker can:
Even when the Publish server is configured with a reader password ( Publish.Auth.Enable=true ), the same primitives are available to any user holding a legitimate Reader account — converting an intended low-privilege viewer into a full Administrator.
The advisory notes that this vulnerability is class-identical to seven prior GHSAs against SiYuan’s Publish subsystem (GHSA-jqwg-75qf-vmf9, GHSA-f9cq-v43p-v523, GHSA-fmh9-gpqh-g53g, GHSA-6r88-8v7q-q4p2, GHSA-gmmv-4cc5-wr9r, GHSA-px3c-cf92-9g83, GHSA-7m5h-w69j-qggg), all of which involved Publish Reader escalation via a missing admin gate on sensitive endpoints. The MCP integration simply added a new — and far more powerful — surface to the same recurring pattern.
SiYuan released v3.7.2 on July 14, 2026, fixing the vulnerability in commit df51c2bda696 . The fix applies defense in depth at three layers:
File: kernel/mcp/server.go
The route now requires both CheckAdminRole (caller must be Administrator) and CheckReadonly (write operations blocked for non-admin contexts). A RoleReader JWT — anonymous or not — is rejected at the middleware layer before reaching the handler.
File: kernel/mcp/tools/file.go
Even with the admin gate, the file tool now explicitly refuses to resolve conf/conf.json , aligning with the HTTP file API’s existing blocklist ( refuseToAccess in kernel/api/file.go ). This is a belt-and-suspenders measure: if the admin gate is ever bypassed, the credential file remains unreachable.
File: kernel/server/proxy/publish.go
The fix adds an isPublishAdminPath() function that returns 401 Unauthorized for admin-scoped paths when the Publish server is in anonymous mode. The blocked path prefixes include:
Plus specific write/sensitive endpoints under /api/file/* ( putFile , copyFile , removeFile , renameFile , etc.). Public read endpoints needed for rendering published content ( getFile , getDoc , /assets/ , /appearance/ , /stage/ ) are explicitly allowed.
This means the anonymous JWT is no longer forwarded to any admin-scoped path, closing the composition at the proxy layer.
Caveat: If your Publish server was exposed to the internet in anonymous mode on v3.7.1, assume compromise. The attack is silent — no crash, no error log, just HTTP 200 responses. Rotate all credentials and audit the workspace for modified or planted files.
CVE-2026-66012 is not just a SiYuan bug. It is an early warning a class of vulnerabilities that will proliferate as the MCP ecosystem grows.
MCP’s value proposition — standardized tool discovery and invocation — is also its security risk. Every MCP server exposes a tool registry that describes capabilities in structured JSON. An attacker who can reach the tools/list endpoint gets a self-documenting inventory of attack surface. And unlike REST APIs, where each endpoint typically has its own authorization logic, MCP tools are often dispatched through a single handler that applies uniform — and uniformly insufficient — authorization.
In our article on LLaMA-Factory’s WebUI RCE via hardcoded trust_remote_code , we saw how the rush to integrate AI capabilities created a critical vulnerability. CVE-2026-66012 is the same story with a different protagonist: the MCP integration was added to SiYuan’s existing HTTP server, inherited its generic auth middleware, and exposed administrative-grade file operations to any authenticated principal — including anonymous readers.
The lesson for developers integrating MCP into their applications:
NIST National Vulnerability Database — CVE-2026-66012: GitHub Security Advisory GHSA-cvhv-7xhj-xjp8: Fix Commit (df51c2bda696): VulnCheck Advisory: SiYuan v3.7.2 Release: SiYuan Official Website: Model Context Protocol Specification: CWE-862 — Missing Authorization (MITRE):
The full story
This article is one source in a clustered incident — the cluster page carries the summary, timeline and every other outlet covering it.
