The Reference Implementation Was the Warning: Seven CVEs Across MCP’s Own Servers and Desktop Commander

On 8 November 2026, a researcher identifying as ShieldPi published seven related disclosures — CVE-2026-41901 through CVE-2026-41907 — against two of the most-trusted codebases in the Model Context Protocol ecosystem: the official MCP reference servers (mcp-server-git, mcp-server-sqlite, mcp-server-fetch, maintained under modelcontextprotocol/servers) and the popular third-party Desktop Commander server (@wonderwhy-er/desktop-commander on npm). Four issues sit against the reference implementations; three against Desktop Commander. Every one of them converts something an operator assumed was inert — a read-only git tool, a SELECT-only query path, a fetch guard, a default blocklist — into code execution, file writes, or network access. Readers of this site’s October coverage of CVE-2026-104120 on the reference fetch server and the June path-traversal record on the reference git server will recognize the pattern: the servers that ship as the ecosystem’s worked examples keep teaching the same lesson about agent-adjacent trust boundaries.

The git server pair: read-only tools that execute what the repository says

The severest of the four reference-server findings is CVE-2026-41903 (CWE-78): mcp-server-git invokes Git with the repository’s own configuration intact. git_status honors core.fsmonitor, git_checkout runs post-checkout, and git_commit runs pre-commit — nothing disables hooks, fsmonitor, or the pager for MCP operations. A repository planted with an executable core.fsmonitor or an executable .git/hooks/post-checkout therefore gets arbitrary code execution on the host as the MCP server user from tools annotated read-only: the disclosure’s reproduce case calls git_status and a marker file appears outside the repo. State the precondition plainly, as the report does: the malicious .git/config or hook must already be planted and cannot be created through the MCP tools alone, nor does it arrive via a normal git clone — the realistic vector is an extracted tarball or a synced folder, exactly the ways agent workspaces ingest code. The suggested fix is correspondingly narrow: run Git for MCP operations with hooks, fsmonitor, and pager disabled (-c core.hooksPath=/dev/null -c core.fsmonitor= -c core.pager=cat) and sanitize Git-related environment variables before opening repositories.

Its sibling, CVE-2026-41902 (CWE-22), is quieter and arguably harder to spot in review. validate_opened_repo in mcp-server-git 2026.11.4 confines the worktree, git dir, and common dir — but never validates .git/objects, objects/info/alternates, or a symlinked objects/ directory. A crafted repository inside the allowed root that points objects/info/alternates at an outside repository’s object database (or symlinks objects/ to an outside pool) lets git_show return outside commits and blob content, and lets git_checkout and git_create_branch materialize outside history into the crafted worktree. The report includes the negative control that makes the finding: the same call against a plain sibling repo without alternates correctly fails. Impact is cross-repository disclosure plus write-priming of the crafted worktree from an outside object store — the confinement check measured three paths and missed the fourth one Git itself follows on every object lookup.

The sqlite allowlist that wasn’t, and the fetch guard’s blind spot

CVE-2026-41901 (CWE-22 primary, CWE-862) targets mcp-server-sqlite 2025.4.25, whose write_query rejects only statements starting with SELECT and forwards everything else to cursor.execute(). Because SQLite’s VACUUM INTO is self-committing, an attacker-controlled tool call — write_query {"query":"VACUUM INTO '/tmp/escaped.db'"} — writes a complete copy of the configured database to any path writable by the server process, verified in the report by opening the escaped copy cleanly. The same surface also executes DROP TABLE, CREATE TRIGGER (whose body smuggles multi-statement DML that fires on later inserts), ALTER TABLE, and PRAGMA — all outside the documented INSERT/UPDATE/DELETE contract. This is the textbook prefix-check failure: one forbidden keyword where a per-tool statement grammar belonged. Note the packaging wrinkle the disclosure flags — the PyPI mcp-server-sqlite is archived upstream, so the fix (parse statements; read path takes SELECT only, write path takes INSERT/UPDATE/DELETE only, DDL/PRAGMA/VACUUM/triggers outside create_table rejected) has to land wherever the code is actually vendored now.

CVE-2026-41904 (CWE-918) is the most honestly scoped of the seven. _is_public_address in mcp-server-fetch 2026.11.4 and 2026.10.24 unwraps IPv4-mapped and NAT64 forms but omits deprecated IPv6 site-local fec0::/10 and leans on ip.is_global, which returns true for fec0::1 — so the SSRF guard permits a request to a non-public destination. Reproduced on both versions against a 36-case matrix in which every other loopback, RFC1918, link-local, redirect, 0.0.0.0, [::], and full-octal-IPv4 form was correctly refused. And the report states the reachability limit up front: on a stock modern kernel fec0::1 usually times out, and the translated [::ffff:0:127.0.0.1] form was correctly blocked — this is a guard-classifier correctness issue, not a demonstrated metadata-service exploit on default hosts. Credit where due: a disclosure that ships its own negative results and reachability caveats is easier to prioritize correctly, and this one does. The fix is a two-line-shaped change with a principle attached: explicitly block fec0::/10 and ::ffff:0:0/96, and deny by default on not ip.is_global plus reserved/site-local checks.

Desktop Commander: an SSRF with no guard at all, and a blocklist that empties itself

The three Desktop Commander findings escalate faster because the missing controls are structural rather than edge-case. CVE-2026-41905 (CWE-918): readFile(path, {isUrl:true}) routes to readFileFromUrl, which issues a plain fetch(url) with only a timeout — validatePath is bypassed entirely, and there is no public/private-IP, loopback, link-local, or metadata-address check of any kind. The feature is even advertised (“Can fetch content from URLs when isUrl parameter is set to true”), and the report’s canary — reading a loopback listener and then http://169.254.169.254/latest/meta-data/ from inside tool calls despite a narrow allowedDirectories — confirms an MCP caller can pull loopback, RFC1918, and cloud-metadata hosts straight into the conversation. Directory scoping simply does not apply to the URL path.

CVE-2026-41906 (CWE-1188, CWE-862) is the one to show engineers who think defaults protect them. getDefaultConfig() seeds blockedCommands with the dangerous-command list (sudo, dd, shutdown, …), but validateCommand computes config.blockedCommands || [] — and an empty-but-defined array passes the || fallback, iterates over nothing, and blocks nothing. Since set_config_value accepts blockedCommands: [] with no emptiness check, any caller that can set configuration can silently disarm every command filter, contradicting the fail-closed comment in the same function’s error handler. The canary is crisp: sudo id blocked before, allowed after. CVE-2026-41907 (CWE-78, CWE-862) then walks through the remaining door: start_process with command exactly node:local passes validateCommand (the literal matches no blocklist entry), subsequent interact_with_process input is written to a temp .mjs file and executed via the Node binary — arbitrary fs/http/child_process with no per-call command review — and the StartProcessArgsSchema shell argument passes an arbitrary string to the spawned interpreter, bypassing the command-string blocklist entirely. The report’s recommendation doubles as documentation advice: treat node:local as unrestricted execution, full stop.

What to do

  • Assume the reference servers are exposed until patched, and scope them like it. All four reference-server issues were open at time of writing with no fix version confirmed in the disclosures. Run MCP servers — especially git and sqlite — as a least-privilege user, on hosts where “any writable path” and “any planted hook” buy an attacker as little as possible, and keep ingested repositories (tarballs, synced folders, shared volumes) out of the allowed roots.
  • Apply the reporters’ hardening directly if you vendor these servers. Disable hooks/fsmonitor/pager for MCP-driven Git invocations, resolve objects/info/alternates and symlinked objects/ against the authorized root (or reject alternates), replace the SELECT-prefix check with a per-tool statement grammar, and deny-by-default on non-global addresses including fec0::/10. These are small, reviewable changes — the kind to backport rather than await upstream.
  • If you run Desktop Commander, audit three settings today. Remove or front isUrl fetching with your own egress check, assert blockedCommands is non-empty after every config change (an empty list is fail-open, not fail-closed), and treat node:local plus any custom shell argument as arbitrary execution in your threat model. Then apply the same checks to every MCP server you run: this week’s census of 10,659 public MCP configs found most of the ecosystem installs whatever the registry serves today — unpinned servers plus unpatched guards is the combination that converts a disclosure like this one into incidents.
  • For builders: validate the thing the operation actually touches. All seven flaws share one geometry — the check inspected a proxy (the worktree path, the statement prefix, the is_global flag, the non-empty-looking array, the command string) while the operation followed something else (the alternates pointer, the VACUUM target, the site-local address, the empty list, the temp .mjs). The MCP Inspector 2.14.0 sandbox-escape batch and the MCPhantom deprecated-server findings keep circling the same point: agent tooling needs deny-by-default at the enforcement point, not recognition at the front door.

Verification note: all seven disclosures were read first-hand in full via the GitHub API on 8 November 2026 (modelcontextprotocol/servers issues #5487/CVE-2026-41902, #5488/CVE-2026-41903, #5491/CVE-2026-41901, #5492/CVE-2026-41904 — each created 8 November 2026 ~15:51 UTC, all open at read time; wonderwhy-er/DesktopCommanderMCP issues #1015/CVE-2026-41905, #1017/CVE-2026-41906, #1016/CVE-2026-41907 — each created ~15:54 UTC, all open at read time; duplicate filings #5489/#5490/#5494/#5495/#5497/#5500/#5503 and #1033/#1035 identified via their own bodies and comments and excluded in favor of the unique issues above). CVE identifiers, CWE assignments, affected versions (mcp-server-git and mcp-server-fetch 2026.11.4; fetch also 2026.10.24; mcp-server-sqlite 2025.4.25), reproduce claims, canary outputs, and the 41904 reachability caveat are carried as reported by ShieldPi (research@shieldpi.io); NVD records were not yet observed at write time and no CVSS scores are stated here. No repository was cloned, no MCP server was run, no payload was executed, no token was tested, and no vendor was contacted. Brave Search was used for discovery only.

Sources: