MCP Security: Real Attacks, the Controls That Stop Them, and an Approval Checklist
MCP security is mostly your job: the spec makes authorization optional and cannot tell a poisoned tool from an honest one. This guide covers the real MCP attacks and CVEs to date, a threat-to-control table mapped to OWASP, and a server approval checklist where every item is a pass/fail test.
MCP security means making sure a Model Context Protocol server, and the agent that calls it, cannot become an attacker’s route into your data. The protocol gives you OAuth-based authorization for HTTP servers. It does not vet servers, read tool descriptions for you, or stop a trusted server from turning hostile. That part is yours.
Below: the real attacks so far, a threat-to-control table, and a server approval checklist of pass/fail tests.
What MCP security covers, and what the spec leaves to you
The current spec revision is dated 2026-07-28. Its authorization page says authorization is OPTIONAL. HTTP servers that support it SHOULD follow the OAuth 2.1 flow. Servers on the stdio transport SHOULD NOT. They “retrieve credentials from the environment” instead. So a local server is as safe as whatever its process can reach.
The tools page puts the rest on the server. Servers MUST validate all tool inputs, implement access controls, rate limit tool invocations, and sanitize tool outputs. Clients MUST treat tool annotations as untrusted unless they come from trusted servers. The protocol checks none of this. A server that skips every rule still speaks valid MCP.
Practice lags the spec. In a study published in October 2025, Astrix analyzed more than 5,200 open-source MCP server implementations. About 88% required credentials. 53% relied on static API keys or personal access tokens, which Astrix describes as long-lived and rarely rotated. Only 8.5% used OAuth.
Decision rule: “it speaks MCP” tells you nothing about security. Approve servers one at a time, against tests. For each spec revision’s changes, see the MCP roadmap and revision timeline.
What are the main MCP security risks? The attacks so far
Every row below is a published case, with the date it went public.
| Date | Case | What failed | Class |
|---|---|---|---|
| Apr 1, 2025 | Invariant Labs tool poisoning demo | Hidden tool description text made Cursor read SSH keys | Tool poisoning |
| May 26, 2025 | GitHub MCP issue injection | A public issue made an agent leak private repo data | Indirect prompt injection |
| Jun 13, 2025 | CVE-2025-49596, MCP Inspector | Unauthenticated local proxy; a web page could run commands | Exposed local server |
| Jul 9, 2025 | CVE-2025-6514, mcp-remote | A server’s OAuth endpoint value became an OS command | Malicious server vs. client |
| Aug 5, 2025 | CVE-2025-54136, “MCPoison” | Approved config swapped later, no new prompt | Rug pull |
| Sep 25, 2025 | postmark-mcp backdoor | Fake Postmark server BCC’d outgoing emails to an attacker | Malicious package |
| Jan 20, 2026 | mcp-server-git CVEs | Path traversal and argument injection in the reference server | Vulnerable server code |
| Aug 12, 2026 | Deadbugz campaign | Server rewrote its tool metadata after three calls | Rug pull, supply chain |
Three patterns stand out. The model reads tool metadata as instructions. Trust granted once is rarely re-checked. And many failures are ordinary bugs in software that happens to speak MCP. Standard AppSec handles the bugs. The first two need MCP-specific controls.
Tool poisoning and rug pulls: why install-time approval is not enough
Tool poisoning works because the client passes each tool’s description and schema to the model as context. Invariant defined it as “malicious instructions” in tool descriptions that “are invisible to users” but visible to the model. Their poisoned add tool told the agent to read SSH keys. A variant they called shadowing let a malicious server redirect a trusted server’s send_email tool to the attacker, even when the user named another recipient.
A rug pull is the same attack with a delay: pass review, then change. MCPoison showed the client side. Cursor tied trust to an MCP entry’s name, not its contents, until Cursor 1.3 (July 29, 2025) required re-approval for any config change. Deadbugz showed the server side. Pillar found 23 pull requests, opened within 74 minutes on August 10, 2026, adding a server called productivity-suite. After a client’s third tool call, its tools/list and prompts/get responses told the agent to collect SSH keys, AWS credentials, shell history, and Kubernetes config, and to hide it. None had been merged through GitHub’s merge mechanism when Pillar reviewed them: 19 were closed and 4 were still open.
The 2026-07-28 tools page helps here. The tool list “MUST NOT vary per-connection or as a side effect of other requests.” Deadbugz-style gating breaks that rule, so it is testable. But the list MAY change over time, with a list_changed notification. A legitimate update and a rug pull look the same on the wire.
Control: fingerprint the tool definitions at approval and re-approve on any change. The function takes the tools array, not the whole tools/list result, so merge all pages first if there is a nextCursor. It covers tools only. Deadbugz also changed prompts/get output, so hash each prompt the same way.
import hashlib, json
def tool_fingerprint(tools: list[dict]) -> str:
"""SHA-256 over the canonical JSON of a tools/list result."""
canon = json.dumps(sorted(tools, key=lambda t: t["name"]),
sort_keys=True, separators=(",", ":"), ensure_ascii=False)
return hashlib.sha256(canon.encode("utf-8")).hexdigest()
Early detection: alert when a fingerprint differs from the approved one. Also alert when it changes between two calls with the same token and no list_changed notification arrived. Servers SHOULD send one to clients subscribed through subscriptions/listen. If you subscribed, a silent change has no spec-sanctioned explanation. Treat it as high severity and investigate.
Confused deputy, token passthrough, and other auth failures
A confused deputy attack hits MCP servers that proxy a third-party API. The MCP security best practices list four conditions: one static client ID at the third party, dynamic client registration, a third-party consent cookie, and no per-client consent at the proxy. With all four, an attacker registers a client with their own redirect URI and sends the victim a link. The cookie skips the consent screen, and the authorization code lands on the attacker’s server. The fix is a MUST: per-client consent before the third-party flow, and exact matching on redirect URIs.
Token passthrough means forwarding whatever token the client sends. The authorization spec forbids it: servers “MUST NOT accept or transit any other tokens.” Audience binding enforces the rule. Clients send an RFC 8707 resource parameter, and servers reject any token not issued for them. Passthrough also breaks attribution: downstream logs “may show requests that appear to come from a different source with a different identity.”
Three smaller rules close the remaining gaps. Clients MUST validate the iss value in authorization responses per RFC 9207, which blocks mix-up attacks between authorization servers. Clients deployed on servers MUST consider SSRF, because a malicious server can point OAuth discovery at addresses like 169.254.169.254, the cloud metadata endpoint. And wildcard scopes such as files:* turn one stolen token into broad access.
The roadmap post covers how the 2026-07-28 auth changes work, including the deprecation of Dynamic Client Registration. Per-agent credentials are covered in AI agent identity.
Malicious and vulnerable servers: the supply chain problem
A third-party MCP server is a dependency that runs with your credentials. The cases above show four ways that goes wrong.
Impersonation. The postmark-mcp package was not from Postmark. Per The Register, the attacker “built trust over 15 versions, then added a backdoor in version 1.0.16.” It drew about 1,500 downloads in a week.
Hostile server, vulnerable client. In CVE-2025-6514 (CVSS 9.6), mcp-remote 0.0.5 to 0.1.15 passed a server-supplied authorization_endpoint to a URL opener that, on Windows, ran PowerShell. Version 0.1.16 fixed it. The security best practices now say clients MUST NOT use shell commands to open URLs and MUST reject javascript: and similar schemes.
Exposed local server. MCP Inspector before 0.14.1 (CVE-2025-49596, CVSS 9.4) ran a local proxy that launched commands without authentication. A malicious web page could reach it through 0.0.0.0 or DNS rebinding. The fix added a session token and origin checks.
Ordinary bugs in reference code. Cyata found path traversal and argument injection in Anthropic’s own mcp-server-git, fixed in versions 2025.9.25 and 2025.12.18. Chained with the Filesystem server and triggered by prompt injection, they reached code execution. Cyata’s CEO called it “the canonical Git MCP server, the one developers are expected to copy.”
Decision rule: a local stdio server is code execution with your user’s privileges. Run it in a sandbox with no access to home-directory secrets. Approve a remote server like a SaaS vendor that receives your data.
How OWASP classifies MCP risk
The OWASP Top 10 for Agentic Applications, released December 9, 2025, gives your risk register shared IDs. Four entries cover most MCP risk. ASI02, Tool Misuse and Exploitation, defines tool poisoning as compromising “MCP tool descriptors, schemas, metadata, or routing information.” ASI03 is Identity and Privilege Abuse. ASI04, Agentic Supply Chain Vulnerabilities, names a “Compromised MCP / Registry Server” as an attack scenario. ASI05 is Unexpected Code Execution. The document also defines “least agency”: avoid unnecessary autonomy, because “deploying agentic behavior where it is not needed expands the attack surface without adding value.”
The OWASP Top 10 for LLM Applications now has a 2026 edition, which replaces the 2025 list. For MCP, the key entry is LLM03:2026, Excessive Agency. Its three root causes are excessive functionality, excessive permissions, and excessive autonomy. One prevention item: “execute tools in user’s context.”
My recommendation: tag every MCP finding with an ASI or LLM ID, since auditors already read those lists. For goal hijack through retrieved content, see prompt injection in AI agents.
The MCP threat-to-control table
Each row maps a threat to the control that stops it and the layer that owns that control.
| Threat | Real case | Control that stops it | Owner | OWASP |
|---|---|---|---|---|
| Tool poisoning | Invariant demo | Review full descriptions before approval | Client, approval team | ASI02 |
| Rug pull | MCPoison, Deadbugz | Fingerprint definitions; re-approve on change | Client or gateway | ASI04 |
| Tool shadowing and name collisions | Invariant send_email | Prefix tool names by server | Client or gateway | ASI02 |
| Malicious package | postmark-mcp | Verify publisher; pin by hash | Approval team | ASI04, LLM04 |
| Vulnerable server code | mcp-server-git | Input validation; track advisories | Server owner | ASI05 |
| Indirect injection via tool results | GitHub MCP | Least data per agent; confirm writes | Client, server | ASI01, LLM01 |
| Confused deputy | Spec attack flow | Per-client consent; exact redirect URI match | Proxy server | ASI03 |
| Token passthrough, wrong audience | Spec anti-pattern | RFC 8707 audience check; separate downstream credential | Server | ASI03 |
| Mix-up attack | Spec attack flow | Validate iss per RFC 9207 | Client | ASI03 |
| Malicious OAuth URL or SSRF | CVE-2025-6514 | https only; no shell; block private IPs | Client | ASI05 |
| Exposed local server | CVE-2025-49596 | Loopback, token, origin checks | Server, client | ASI05 |
| Over-broad or static credentials | Astrix: 53% static keys | Narrow, short-lived, per-server tokens | Server owner | ASI03, LLM03 |
The pattern: narrow what each server can do, pin what it says it does, and check every token’s audience.
MCP server approval checklist: pass/fail tests
The MCP gateway guide covers the approval workflow. This checklist is the test step inside it. Run it in a sandbox with fake data and fresh tokens.
| # | Test | How to run it | Pass if |
|---|---|---|---|
| 1 | Publisher and pin | Compare publisher with vendor docs; read the lockfile | Vendor-published or your own fork; exact version and hash |
| 2 | Known CVEs | Check GitHub advisories and OSV for the package | No open advisory at the pinned version |
| 3 | Descriptions | Read every tool, parameter, and prompt description | No instructions about other tools, files, secrets, or hiding |
| 4 | Fingerprint | Hash the merged tools array with the function above; hash each prompt too | Hashes stored in the register |
| 5 | Stability | Hash the tools array, make five calls, hash again, same token | Identical hash |
| 6 | No token | Call the endpoint with no Authorization header | 401; resource metadata found via the WWW-Authenticate header or the well-known URI |
| 7 | Wrong audience | Send a valid token issued for a different resource | 401 |
| 8 | Scope | Call a write tool with a read-only token | 403 with insufficient_scope |
| 9 | Passthrough | Make one call; read the downstream audit log | Downstream sees the server’s own credential |
| 10 | State handles | Create a handle as user A; use it as user B | Rejected |
| 11 | Input validation | Send ../ paths and arguments that start with -- | Rejected with isError: true; nothing written |
| 12 | Egress | Log traffic during sample tasks | Only declared hosts; email goes only to the recipients you set |
| 13 | Local reach | Call the local port from a browser page; read ~/.ssh from inside the server’s runtime | Both refused |
Scoring. Tests 1, 3, 5, 7, and 9 are hard gates. One failure means no approval: in each case above, one gap was enough. For a read-only server on non-sensitive data, tests 8 and 10 can be marked not applicable. Re-run tests 2 to 5 on every version change, and run test 5 on a schedule in production. The gate choices are my judgment, not a standard.
Bottom Line
MCP security is an approval and monitoring problem more than a protocol problem.
The 2026-07-28 spec has strong auth rules for HTTP servers, but it cannot vet a server or stop one from changing. Real attacks so far came through poisoned descriptions, swapped configs, fake packages, and plain bugs.
Run the 13 tests on every server you already use, starting with anything that can send, write, or execute. Then put the fingerprint check on a schedule.
FAQ
Is MCP secure by default?
No. Authorization is optional, and stdio servers take credentials from the environment. In Astrix’s October 2025 study, 79% of API keys were passed through environment variables.
What is the difference between tool poisoning and prompt injection?
Invariant describes tool poisoning as a specialized form of indirect prompt injection. The difference is timing. Poisoned metadata reaches the model when a client lists tools, before any call. A typical injection arrives later, inside data a tool returns.
Can two MCP servers expose tools with the same name?
Yes. The spec scopes tool name uniqueness to a single server, and says the server name a client sees is not guaranteed unique either. Clients that combine servers should prefix tool names with a server identifier they control.
Do stdio MCP servers need OAuth?
No. The spec says stdio implementations should not follow the OAuth flow. The control is consent plus a process boundary. A client that offers one-click local setup must show the exact command, untruncated, and get explicit approval before running it.
Which MCP vulnerabilities should I check for first?
Confirm these minimum versions: mcp-remote 0.1.16, MCP Inspector 0.14.1, mcp-server-git 2025.12.18, and Cursor 1.3. Then search every MCP client config for productivity-suite, the Deadbugz server name.
Should I still use the OWASP LLM Top 10 2025?
Use the 2026 edition for new work. In it, Prompt Injection is LLM01, Excessive Agency is LLM03, Supply Chain is LLM04, and Hidden Context Exposure is LLM08. Pair it with the Agentic Top 10 for agent and MCP risks.
Three Ways to Work With TMA
Need an agent built? We deploy production AI agents in your infrastructure. Working pilot. Real data. Measurable ROI. → Schedule Demo
Want to co-build a product? We’re not a dev agency. We’re co-builders. Shared cost. Shared upside. → Partner with Us
Want to join the Guild? Ship pilots, earn bounties, share profit. Community + equity + path to exit. → Become an AI Architect
Need this implemented?
We design and deploy enterprise AI agents in your environment with measurable ROI and production guardrails.
About the Author
Chase Dillingham
Founder & CEO, TrainMyAgent
Chase Dillingham is the founder and CEO of TrainMyAgent. He spent 15+ years as an enterprise architect deploying production systems. Now he builds AI agent platforms that deliver measurable ROI.
Connect with Chase Dillingham on LinkedIn