Skip to main content

Common MCP Vulnerabilities and How to Fix Them

Short answer

The MCP spec names several attacks on servers, clients and the OAuth sign-in around them. The five below cover most of the real damage: confused deputy, token passthrough, SSRF, session or state handle hijacking, and local server compromise. Each one has a specific fix in the spec, such as checking a token's audience, getting consent for each client, blocking private addresses, and showing the exact command before a local server runs. Real CVEs in mcp-remote, the MCP Inspector, the filesystem server and Cursor are listed with their fixed versions.

Tested withMCP spec 2026-07-28MCP spec 2025-11-25 (session hijacking section)Checked 20 Oct 2026

The five attacks at a glance​

AttackWho it hitsWhat goes wrongThe fix in short
Confused deputyProxy serversA malicious client gets an authorization code without the user's consentGet consent for each client before the third-party sign-in
Token passthroughServersA server accepts a token issued for another service, or forwards it upstreamAccept only tokens that name your server, and never forward them
SSRFClients, and authorization servers that fetch client metadataThe client fetches an address a malicious server supplied, such as a cloud metadata endpointRequire HTTPS, block private address ranges, check every redirect
Session and state handle hijackingServers that keep state between callsA guessed or stolen ID is used to act as another userRandom IDs, bound to the authenticated user, checked on every request
Local server compromiseLocal servers and the clients that start themA startup command or server runs with your user's full privilegesShow the exact command, require approval, sandbox the server, restrict local access

The spec describes these in its security best practices page, with the MUST and SHOULD rules for each. The sections below follow the same order.

1. Confused deputy in MCP proxy servers​

An MCP proxy server connects MCP clients to a third-party API. It acts as one OAuth client to that API, and the third-party sign-in server often needs a single static client ID for all of its requests. The attack needs four things to be true:

  • the proxy uses one static client ID with the third-party server,
  • the proxy lets any MCP client register itself dynamically, so each one gets its own client ID,
  • the third-party server sets a consent cookie after the first sign-in,
  • the proxy doesn't ask the user to consent to each new client before it forwards the request.

A user signs in through the proxy once, and the third-party server records their consent in a cookie. Later, an attacker registers a client whose redirect URI points at a server they control, then sends the user a link to it. The cookie makes the third-party server skip its consent screen, the authorization code goes to the attacker, and the attacker exchanges it for an MCP token that acts as the user.

The fix belongs in the proxy:

  • Show an MCP-level consent page for each dynamically registered client before you forward to the third-party server. The page names the client, the third-party scopes, and the redirect URI the tokens will go to.
  • Store the approved client IDs for each user, and check that record before you start the third-party flow.
  • If you use a consent cookie, give it the __Host- prefix, set Secure, HttpOnly and SameSite=Lax, and bind it to the specific client ID.
  • Compare redirect_uri with the registered value exactly. Don't use wildcards or pattern matching.
  • Generate a random state value for each authorization request. Set its cookie only after the user approves consent, delete it after one use, and let it expire after a short time, such as 10 minutes.

2. Token passthrough​

Token passthrough is an anti-pattern where an MCP server accepts a token from a client without checking that the token was issued to the server, and then passes it on to the downstream API. The spec calls it out by name and forbids it. It causes two separate problems. The first is audience validation: the server accepts a token meant for some other service. The second is forwarding: the server sends that token upstream, which can confuse the downstream API about who is calling it.

Passthrough also bypasses controls. If a client can use the token directly against the upstream API, the server's rate limits, request checks and logs are skipped. The downstream logs show the upstream API as the caller, so the audit trail stops showing which client acted.

Here's a worked case. A calendar server at mcp.example.com receives a token whose audience is https://mail.example.com. The signature is valid, so a server that only checks the signature would accept it and forward it to the mail API. A correct server rejects the token because the audience doesn't name the server itself. The spec's rule for invalid tokens is an HTTP 401 response.

The fix:

  • Check every access token before you act on it. The audience must name your server. The authorization spec says servers must reject tokens that don't include them in the audience claim, and must return HTTP 401 for invalid or expired tokens.
  • Never pass a client's token to an upstream API. If your server calls another API, it gets its own token from that API's authorization server.
  • Clients must send the resource parameter (RFC 8707) when they ask for a token, so the token is issued for your server in the first place.

The Go SDK's protocol documentation makes the same split: checking that a token was issued for the server is the job of the token verifier you write. The SDK doesn't do it for you. Authorization and OAuth shows how to build the check.

3. SSRF in MCP clients​

Server-side request forgery (SSRF) tricks a program into making requests to addresses its owner didn't intend. In MCP, the client fetches URLs that come from the server during OAuth discovery: the resource_metadata URL in the WWW-Authenticate header, the authorization servers listed in the protected resource metadata, and the endpoints in the authorization server metadata. A malicious server can fill those fields with internal addresses.

A server might reply with a 401 whose resource_metadata points at http://169.254.169.254/, the cloud metadata address. A client that follows it without checking can hand the instance's credentials to the server, and any error text it returns can leak details of the internal network. A client on a corporate network can also be used to reach internal services the attacker can't see.

The fix, as the spec lists it:

  • Require HTTPS for every OAuth URL in production. Accept http:// only for localhost, 127.0.0.1 and ::1 during development.
  • Block private and reserved ranges: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 127.0.0.0/8, 169.254.0.0/16, fc00::/7, fe80::/10 and ::1.
  • Validate each redirect target the same way. Don't follow redirects to internal addresses, and consider turning automatic redirects off so you can check every hop.
  • Don't write your own IP parser. The spec warns that octal, hex and IPv4-mapped IPv6 spellings get past custom checks. Use a maintained library, or send discovery requests through an egress proxy that blocks internal destinations.
  • Consider pinning the DNS result between the check and the request. A domain can resolve to a public address when you check it and to an internal one when you connect.

4. Session and state handle hijacking​

In the 2025-11-25 revision, a Streamable HTTP server gave each client a session ID. If an attacker obtained or guessed that ID, they could impersonate the client and inject events into its stream. The 2025-11-25 security page calls this session hijacking.

The 2026-07-28 revision made the protocol stateless, so there are no protocol-level sessions. A server that needs state across calls, such as a shopping basket or a workflow, now mints its own handle, returns it in a tool result, and receives it back as a tool argument. The same attack applies to that handle, and the current spec calls it state handle hijacking. An attacker who gets or guesses the handle can operate on another user's state.

The fix:

  • Verify every inbound request if your server uses authorization. Possession of a session ID or state handle is never authentication.
  • Generate IDs with a secure random number generator. Avoid sequential or predictable values, and expire them.
  • Bind stored state to the authenticated user. The spec suggests keying it as <user_id>:<handle>, where the user ID comes from the verified token and not from the client. Reject a handle presented by any other user.

5. Local server compromise​

A local MCP server is a program that runs on the user's machine, started by the client or by the user. It runs with the user's privileges, and the spec says that without proper consent and sandboxing, three attacks become possible: a malicious startup command in the client's configuration, a malicious payload inside the server, and an insecure server left running on localhost that an attacker reaches through DNS rebinding.

A real example from the Cursor CVE: an agent that had been fed a crafted prompt wrote a new entry into .cursor/mcp.json. Cursor started the new server as soon as the file was written, before the user could reject the change.

"slack_summary": {
"command": "touch",
"args": ["~/mcp_rce"]
}

That entry is harmless, but the same shape can run any command. The second example is the config that starts a remote server through mcp-remote. Its http:// address is the insecure form that the JFrog write-up describes:

{
"mcpServers": {
"remote-mcp-server-example": {
"command": "npx",
"args": ["mcp-remote", "http://remote.server.example.com/mcp"]
}
}
}

The fix, from the spec:

  • A client that offers one-click setup must show the exact command, without truncation, call it a potentially dangerous operation, and require explicit approval before it runs.
  • Clients should also flag commands that use sudo, rm -rf, network tools, or paths like your home directory and SSH keys, and should run servers with the least privilege they need. Sandbox local servers covers how to do that.
  • A server meant for local use should use stdio, so only its client can reach it. If it uses HTTP, it should require an auth token, or use a Unix domain socket or another IPC mechanism with restricted access.
  • Use https:// for remote servers through mcp-remote, and use mcp-remote 0.1.16 or later.

Real CVEs in MCP tools​

These are the published vulnerabilities in widely used MCP tools. Each row links to the primary write-up, and the fixed version is the one the advisory names.

CVEProductAffectedFixed inWhat happened
CVE-2025-6514mcp-remote (npm)0.0.5 to 0.1.150.1.16A malicious server returned a crafted authorization_endpoint URL, and the client's command injection ran when it opened that URL. JFrog showed full command execution on Windows.
CVE-2025-49596MCP InspectorBefore 0.14.10.14.1The Inspector's proxy ran stdio commands without authentication. A web page could send those requests through CSRF or DNS rebinding and run commands on the developer's machine. The fix adds a session token and origin checks.
CVE-2025-53110@modelcontextprotocol/server-filesystem0.6.2 and earlier, and 2025.1.14 to before 2025.7.12025.7.1The allowed-directory check matched prefixes, so a sibling folder whose name began with the allowed path could be read and written.
CVE-2025-53109@modelcontextprotocol/server-filesystemSame range as CVE-2025-531102025.7.1 (advisory); 0.6.4 (CVE record)A symlink inside an allowed directory could point at any file on the system. Cymulate reported both filesystem CVEs together as EscapeRoute.
CVE-2025-54135CursorBefore 1.3.91.3.9An agent could create .cursor/mcp.json without approval, and Cursor started the server it contained. Aim Labs called it CurXecute, and a prompt injection could drive it.

If you use one of these tools, update it to the fixed version. If you can't update yet, don't connect it to servers you don't trust, and check your MCP config files for entries you didn't add.

Other attacks the spec covers​

The spec also covers four more attacks, each with its own mitigation. Read the security best practices page for the full rules.

  • Mix-up attacks. A malicious authorization server tricks the client into sending its code to an honest server. The fix is to check the iss parameter on authorization responses (RFC 9207). PKCE alone doesn't stop this.
  • Localhost redirect URI impersonation. A local process can claim to be a legitimate client by binding a localhost port and receiving the code.
  • OAuth authorization URL injection. A malicious server returns a javascript:, file: or shell-injected URL. Clients should accept only http and https, and open URLs without a shell.
  • Scope minimization. A token with broad scopes gives an attacker more to work with if it leaks. Request the smallest set of scopes, and ask for more when an operation needs them.

Common misconceptions​

"HTTPS keeps my MCP server safe." HTTPS stops someone on the network from reading or changing traffic. It does nothing about a malicious server, a token that was issued for another service, or a crafted URL. The JFrog write-up lists HTTPS as one mitigation for CVE-2025-6514, alongside the update to mcp-remote 0.1.16.

"A server on localhost can only be reached by me." The Inspector CVE shows that a web page can send requests to localhost through CSRF or DNS rebinding. Binding to localhost helps, but it isn't a login.

"A valid token means the request is allowed." A valid token proves that someone signed in. It doesn't prove that the token was issued for your server. Check the audience every time.

FAQ​

Are these bugs in the MCP protocol itself?

Mostly they are mistakes in how servers, clients and proxies are built. The spec describes each attack and says what implementers must do to prevent it. The CVEs above were bugs in specific products, and each has a fixed release.

What is the difference between token passthrough and confused deputy?

Token passthrough is a server that accepts a token it wasn't issued and forwards it to an upstream API. Confused deputy is a proxy whose shared client ID lets an attacker get an authorization code without the user's consent. Both let one party act with another party's authority.

Do I need to care about these if I only connect servers that someone else built?

Yes. The client decides whether it fetches OAuth URLs, asks for consent before it runs a local command, and checks what a remote server sends it. Keep your client and any mcp-remote install updated, and check the CVE table above against the versions you run.

Does signing in with OAuth protect me from these attacks?

Not by itself. OAuth gives the server a token, and the server still has to check that the token was issued for it. Consent, redirect URI checks and the state parameter are all part of the flow, and each one can be missing.