An MCP server is a program that exposes tools, data and prompt templates to an AI application over the Model Context Protocol, so an assistant such as Claude or ChatGPT can read your systems and act on them. Securing one means treating every tool description, tool result and token as untrusted, scoping credentials to a single server, and logging every call. The OWASP MCP Top 10 names the ten ways this goes wrong.
This guide is for engineers who build or connect MCP servers and the security leads who approve them. It explains the protocol at the wire level, then walks through all ten OWASP MCP risks with a real example where one exists and a fix for each.
Key takeaways
- MCP is a client and server protocol on JSON-RPC 2.0. A host application runs one client per server; each server offers tools, resources and prompts, and the client can offer sampling, roots and elicitation back.
- Every MCP server is code you run with your credentials. A local server is a process on a developer laptop; a remote one holds OAuth tokens to your SaaS. Both are supply chain.
- The dangerous combination is private data, untrusted content and a way out. Simon Willison calls it the lethal trifecta. Most published MCP incidents fit it.
- The OWASP MCP Top 10 is still maturing. The project page lists the 2025 edition at Phase 3, which OWASP labels "Beta Release and Pilot Testing" and where it gathers feedback, and the item names differ slightly between the page and the repository.
- Fixes are old disciplines on a new channel: least privilege, audience bound tokens, pinned dependencies, argument validation, human approval and an audit log.
Patient zero: a fake email server that copied every message
On 25 September 2025, the email company Postmark published a notice about an npm package called postmark-mcp that it had never written. According to Postmark's statement, a malicious actor impersonated the company's name, "built trust over 15 versions," then added a backdoor in version 1.0.16 that secretly blind copied every email the tool sent to an external address. The npm registry record shows the package was first published on 15 September 2025.
Nothing about the attack was clever at the protocol level. The assistant called a tool to send an email; the server sent it, plus a hidden copy. Neither the model nor the user could see the extra line, because it lived in server code, not in the tool description.
That is MCP security in one story. An MCP server is a program with your credentials, installed from a public registry, sitting in the path of your data. Everything else in this post follows from taking that seriously.
Before and after MCP: the N times M problem
Each era of application architecture moved the trust boundary. Monoliths kept data and logic in one process. Microservices split them across network calls, so authentication moved to gateways. SaaS put company data behind third party APIs, and OAuth became the way to delegate access.
Large language models arrived as a new client for all of those APIs, and the early pattern was bespoke: with N assistants and M systems you needed close to N times M connectors, each with its own auth handling and its own bugs.
MCP collapses that to N plus M. Each assistant implements one MCP client. Each system ships one MCP server. Any client can talk to any server because they share a message format and a lifecycle. The MCP specification describes itself as an open protocol for integrating LLM applications with external data sources and tools, and says it takes inspiration from the Language Server Protocol, which solved the same multiplication problem for editors and programming languages.
The security consequence is easy to miss: one malicious or vulnerable server now reaches every assistant that connects to it. The integration surface shrank; the blast radius did not.
If you want a gentler introduction to the vocabulary first, our posts on what the Model Context Protocol is and on MCP messages, transports and authorization cover the basics. This post goes straight to the moving parts that matter for security.
Anatomy of MCP
The facts in this section come from the 2025-11-25 revision of the specification, the current version at the time of writing. Earlier revisions (2025-03-26 and 2025-06-18) differ mainly in authorization and transport details, so check which revision your SDK implements.
Host, client and server
The architecture page defines three roles:
| Role | What it is | Example | Security owner |
|---|---|---|---|
| Host | The AI application the user sees; creates and manages clients, enforces consent | Claude Desktop, an IDE, an internal agent | The team that deploys the assistant |
| Client | A connector inside the host, one per server, holding a stateful session | The MCP client library inside the host | The host vendor, configured by you |
| Server | A program that exposes tools, resources and prompts | A GitHub, database or email server | Whoever wrote and runs it, which is often not you |
The design principle the spec states is isolation: each client holds a one to one connection with one server, and servers should not be able to read the whole conversation or see into other servers. In practice the host is the only component that sees everything, which is why host policy (approvals, tool allowlists, logging) matters so much.
JSON-RPC 2.0 and the lifecycle
Every MCP message is a JSON-RPC 2.0 request, response or notification. A session has three phases defined on the lifecycle page: initialization, operation and shutdown. In the initialize exchange each side declares its protocol version and capabilities, for example that the server offers tools with change notifications, or that the client supports sampling, roots or elicitation. That negotiation is your first control point: a host that never advertises sampling cannot be asked by a server to run model completions.
Tools, resources and prompts
Servers offer three primitives:
- Tools are functions the model can call:
create_issue,run_query,send_email. The model decides when to call them. This is where side effects live. - Resources are data the application can read, addressed by URI: a file, a table schema, a ticket. The application usually decides what to load.
- Prompts are templates the user picks, such as a slash command.
Here is what tool discovery and a tool call look like on the wire. The description field is plain text that goes straight into the model's context, which is the root of tool poisoning (MCP03).
{ "jsonrpc": "2.0", "id": 2, "method": "tools/list" }
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"tools": [
{
"name": "search_tickets",
"description": "Search support tickets by keyword. Returns ticket id, title and status.",
"inputSchema": {
"type": "object",
"properties": { "query": { "type": "string", "maxLength": 200 } },
"required": ["query"]
},
"annotations": { "readOnlyHint": true }
}
]
}
}
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": { "name": "search_tickets", "arguments": { "query": "refund failed" } }
}
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [ { "type": "text", "text": "[{\"id\":4812,\"title\":\"Refund failed on order 99\",\"status\":\"open\"}]" } ],
"isError": false
}
}
Two lines in the tools page deserve to be printed on every MCP team's wall. First, "there SHOULD always be a human in the loop with the ability to deny tool invocations." Second, clients "MUST consider tool annotations to be untrusted unless they come from trusted servers." A readOnlyHint of true is a claim made by the server, not a guarantee.
Client features: sampling, roots and elicitation
MCP is not one directional. Servers can ask the client for things:
- Sampling lets a server request a model completion through the client, so a server can use the user's model without its own API key. It is powerful and it is a path for a server to put text in front of the model.
- Roots tell a server which filesystem locations or URIs it may operate within. They are advisory boundaries the server is expected to respect, not a sandbox.
- Elicitation lets a server ask the user for structured input mid task. The elicitation page in the 2025-11-25 revision has form mode and URL mode, and says servers must not use form mode to request sensitive information such as passwords or API keys.
Transports: stdio and Streamable HTTP
The transports page defines two standard transports. With stdio, the host launches the server as a child process and exchanges newline delimited JSON over standard input and output. The server runs with the user's privileges on the user's machine. With Streamable HTTP, the server is an independent process with one endpoint that accepts POST and GET, and it may stream responses with Server-Sent Events.
The spec's security warning for Streamable HTTP is specific: servers must validate the Origin header on all incoming connections to prevent DNS rebinding, should bind only to 127.0.0.1 when running locally rather than all interfaces, and should authenticate all connections. Clients must send the negotiated MCP-Protocol-Version header on every request after initialization.
Authorization: OAuth 2.1 and no token passthrough
The authorization page applies to HTTP transports. A protected MCP server acts as an OAuth 2.1 resource server and the MCP client acts as an OAuth 2.1 client. The pieces that matter for security:
| Mechanism | Standard | What it stops |
|---|---|---|
| Protected Resource Metadata | RFC 9728 | Clients guessing where to get tokens; it tells them which authorization server is authoritative |
| Resource Indicators | RFC 8707 | Tokens minted for one server being replayed at another; clients must send the resource parameter naming the server |
| Audience validation | OAuth 2.1 | A server accepting a token issued for a different API |
| PKCE | OAuth 2.1 | Authorization code interception |
| No token passthrough | MCP spec | A server forwarding the client's token to an upstream API, which breaks audit and enables confused deputy attacks |
The wording is unambiguous: MCP servers "MUST only accept tokens that are valid for use with their own resources" and "MUST NOT accept or transit any other tokens." For stdio servers the spec says to take credentials from the environment instead of the OAuth flow.
A client configuration you can reason about
Most desktop hosts read a JSON file that lists servers. A safer configuration pins exact versions, passes only the secret the server needs, and keeps remote servers on HTTPS:
{
"mcpServers": {
"tickets": {
"command": "npx",
"args": ["-y", "@example-org/tickets-mcp@2.3.1"],
"env": { "TICKETS_TOKEN": "read-only-token-for-this-server-only" }
},
"docs-search": {
"type": "http",
"url": "https://mcp.example.com/mcp"
}
}
}
The common copy and paste version runs an unpinned server, inherits a shell full of cloud credentials, and points at plain HTTP. Every OWASP risk below gets worse there.
The lethal trifecta
On 16 June 2025, Simon Willison named the pattern behind most agent data leaks the lethal trifecta: an agent that combines access to private data, exposure to untrusted content, and the ability to communicate externally. Any one is fine. All three together means an attacker who can put text in front of the model can make it read your data and send it out.
MCP makes the trifecta easy to assemble by accident, because each leg often comes from a different server. A database server supplies private data. A ticketing or GitHub server supplies untrusted content written by strangers. An email, HTTP fetch or pull request tool supplies the way out. No single server looks dangerous on its own.
Two published demonstrations show it:
- GitHub MCP, 26 May 2025. Invariant Labs showed that an issue planted in a public repository could steer an agent, asked only to look at open issues, into pulling data from the user's private repositories and publishing it in a pull request on the public one. The GitHub server code was not buggy; the agent's combined permissions were the problem. Our analysis is in the GitHub MCP prompt injection write-up.
- Supabase MCP, July 2025. General Analysis demonstrated a support ticket whose text redirected a developer's assistant into reading private tables and copying them into a reply the customer could see. Their write-up notes the demo used the
service_role, which bypasses row level security, and lists project scoping, read only SQL and tool call review as mitigations. See also our Supabase trifecta post.
Several Top 10 items are different angles on these three legs.
The OWASP MCP Top 10, item by item
The OWASP MCP Top 10 project, led by Vandana Verma Sehgal, publishes a 2025 edition that its roadmap places in Phase 3, "Beta Release and Pilot Testing," ahead of a final release; the repository also notes a next release planned for October 2026. Treat it as a strong, evolving reference rather than a frozen standard. One naming difference is worth knowing: the project page lists MCP06 as "Prompt Injection via Contextual Payloads," while the detailed file in the GitHub repository is titled "Intent Flow Subversion." Both describe instructions hidden in retrieved context that hijack the agent's goal. The project documents are licensed CC BY-NC-SA 4.0; the summaries below are our own.
MCP01: Token Mismanagement and Secret Exposure
What it is. Hard coded credentials, long lived tokens and secrets that end up in model context, protocol logs or debug traces, where prompt injection or a log reader can retrieve them.
How it happens. A stdio server is launched from a shell that has a personal access token with every scope. A remote server logs full JSON-RPC requests, including Authorization headers, to a shared logging cluster. A tool returns a configuration object that includes a database URL with an embedded password, and the model now has it in context and can repeat it into any outbound tool.
Fix.
- Issue one credential per server, with the narrowest scope the server needs, and an expiry measured in hours or days rather than never. Our scoped credentials per MCP server pattern walks through this.
- For remote servers, use the OAuth flow the spec defines, so tokens are audience bound and short lived. Never paste a long lived token into a URL query string.
- Redact secrets at the server boundary before anything enters a tool result or a log.
SENSITIVE_KEYS = {"authorization", "password", "token", "api_key", "secret", "cookie"}
def redact(obj):
if isinstance(obj, dict):
return {k: ("[redacted]" if k.lower() in SENSITIVE_KEYS else redact(v)) for k, v in obj.items()}
if isinstance(obj, list):
return [redact(v) for v in obj]
return obj
log.info("tool_call", extra={"request": redact(request), "result": redact(result)})
- Store secrets in the operating system keychain or a secrets manager and inject them at launch, not in a config file that ends up in a dotfiles repository.
MCP02: Privilege Escalation via Scope Creep
What it is. Permissions granted to an MCP server or agent grow over time, temporary access becomes permanent, and the agent ends up able to modify repositories, change infrastructure or move data it was never meant to touch.
How it happens. A team grants a broad GitHub token to an issue triage server because the fine grained option took longer, then later adds a deployment tool to the same server. Months on, the triage assistant can push to protected branches. The Supabase demonstration is scope creep in miniature: a support workflow ran with a role that bypasses row level security.
Fix.
- Map each tool to the minimum scope it needs, and request scopes per tool or per task, not per server. The spec's security best practices page has a whole section on scope minimization, warning that broad scopes increase token compromise impact and obscure audit trails.
- Use step up authorization. The 2025-11-25 authorization page describes servers returning 403 with an
insufficient_scopeerror so the client can request more scope only when a specific operation needs it. - Review grants on a schedule. A quarterly access review that includes MCP tokens catches the drift.
- Separate read servers from write servers. A read only database server and a separate, approval gated migration server are easier to reason about than one server with both.
Our MCP server permissions checklist has a worked template.
MCP03: Tool Poisoning
What it is. An adversary compromises a tool, its description or its output so that the model receives malicious or misleading context and acts on it.
How it happens. On 1 April 2025, Invariant Labs published the tool poisoning research that gave the attack its name. A tool's description contained instructions addressed to the model, hidden from the user by the host interface, telling it to read sensitive local files and pass them in a parameter. Because descriptions arrive in tools/list and go straight into context, the model treats them as guidance. Variants include a "rug pull," where a server changes its tool descriptions after the user approved it, and shadowing, where one server's description changes how the model uses a different, trusted server's tools. Our posts on tool poisoning and line jumping through tool descriptions go deeper.
Fix.
- Show users the full tool description, not a summary, at approval time.
- Pin tool definitions. Hash the
tools/listresponse when a server is approved and alert or block when it changes, including onnotifications/tools/list_changed.
import hashlib, json
def tool_fingerprint(tools):
canonical = json.dumps(sorted(tools, key=lambda t: t["name"]), sort_keys=True, separators=(",", ":"))
return hashlib.sha256(canonical.encode()).hexdigest()
if tool_fingerprint(current_tools) != approved_fingerprints[server_id]:
quarantine(server_id, reason="tool definitions changed since approval")
- Namespace tools per server in the host so one server cannot redefine another's tool names.
- Treat tool results as data. Wrap them in clear delimiters and do not let content inside a result widen what the agent is allowed to do next.
MCP04: Software Supply Chain Attacks and Dependency Tampering
What it is. A compromised package, image or dependency changes how an MCP server behaves or plants a backdoor at execution level.
How it happens. postmark-mcp is the clean example: an impersonating package that behaved well for 15 releases and then did not. Most MCP servers are installed with npx -y or uvx, which fetch the latest version at launch, so a malicious release reaches users the next time their host starts. Servers also pull their own dependency trees; a compromised transitive package is just as dangerous. And development tooling counts: CVE-2025-49596 in the MCP Inspector, published 13 June 2025, allowed remote code execution in versions below 0.14.1 because the Inspector proxy accepted unauthenticated requests to launch MCP commands over stdio, per NVD and GitHub's advisory.
Fix.
- Pin exact versions in host configuration and lockfiles. Never run unpinned
npx -yagainst a public registry for a server that holds credentials. - Verify the publisher. In the official MCP Registry, server names are reverse DNS namespaces tied to a verified domain or GitHub account, so
sh.safeguard/safeguardcan only be published by whoever controlssafeguard.sh. Similar names from other publishers can exist, which is exactly why you check the namespace, not the display name. See registry namespace impersonation defense. - Install through a proxy that applies policy. A package firewall in front of npm and PyPI can block known malicious, typosquatted and dependency confusion packages before they reach a laptop.
- Generate an SBOM for each server you deploy and track its dependencies for new advisories, the same way you track application dependencies. Our guide to vetting an MCP server before connecting it is a step by step checklist.
MCP05: Command Injection and Execution
What it is. An agent or server builds shell commands, scripts or API calls from untrusted input without validation, so attacker controlled text becomes execution.
How it happens. Three dated, published examples:
- mcp-remote, CVE-2025-6514, published 9 July 2025 with a CVSS 3.1 score of 9.6 (scored by JFrog). Per NVD, the proxy was exposed to OS command injection when connecting to untrusted MCP servers, through crafted input in the
authorization_endpointURL. GitHub's advisory lists 0.1.16 as the first patched version. - mcp-server-git, CVE-2025-68144, published 17 December 2025. Per NVD, versions before 2025.12.17 passed user controlled arguments to git commands without sanitization, so a value such as
--output=...was read as an option rather than a ref, enabling arbitrary file overwrites. Its siblings CVE-2025-68143 (thegit_inittool accepted arbitrary filesystem paths) and CVE-2025-68145 (the--repositoryrestriction was not enforced on later calls) were published the same day. See our mcp-server-git CVE chain post. - The stdio design itself. A stdio server is a command the host runs, so an imported config file is, in effect, a command. See stdio command injection by design.
Fix. Never pass model supplied strings to a shell. Use argument arrays, reject option like values, and check paths against an allowed root.
# Vulnerable pattern: model controlled text reaches a shell
subprocess.run(f"git diff {ref}", shell=True, cwd=repo)
# Fixed pattern: no shell, no option injection, path confined
from pathlib import Path
ALLOWED_ROOT = Path("/srv/repos").resolve()
def safe_diff(repo: str, ref: str) -> str:
repo_path = Path(repo).resolve()
if not repo_path.is_relative_to(ALLOWED_ROOT):
raise ValueError("repository outside allowed root")
if ref.startswith("-"):
raise ValueError("refs may not start with '-'")
subprocess.run(["git", "rev-parse", "--verify", "--end-of-options", ref], cwd=repo_path, check=True, capture_output=True)
out = subprocess.run(["git", "diff", "--end-of-options", ref], cwd=repo_path, check=True, capture_output=True, text=True)
return out.stdout
Then run the server itself with the least privilege the operating system allows: a container or sandbox with no network unless it needs one, a read only filesystem except for a work directory, and no access to the user's home directory.
MCP06: Prompt Injection via Contextual Payloads (Intent Flow Subversion)
What it is. Instructions hidden in retrieved content, such as a ticket, an issue, a web page or a document resource, override the user's original goal. The agent pivots to the attacker's objective while still appearing to do the requested task.
How it happens. This is the trifecta in action. The GitHub and Supabase demonstrations above are both MCP06: the user asked for something benign, a tool returned attacker written text, and the agent's next tool calls served the attacker. The OWASP repository file describes the vulnerable conditions well: no check that the next planned tool call still serves the original request, retrieved text treated as instructions, and system prompt, user intent and untrusted content merged into one flat context.
Fix.
- Break the trifecta per session. If a session has read untrusted content, disable or require approval for tools that can send data out. This one rule breaks the chain in both published demonstrations.
- Require human confirmation for side effects, as the tools page recommends, and show the user the arguments, not just the tool name.
- Anchor intent. Keep the user's original request separate from retrieved content, and check each planned tool call against it with a policy (for example, "a request to summarize issues never creates pull requests").
- Inspect tool output, not only user input. An inline guardrail that scans tool results for injected instructions catches what input filtering misses.
Our prompt injection vectors post catalogs where these payloads hide.
MCP07: Insufficient Authentication and Authorization
What it is. MCP servers, tools or agents fail to verify identity or enforce access control, so anyone who can reach the endpoint can use it, or a token meant for one thing works for another.
How it happens. A test server listens on 0.0.0.0 with no auth. A remote server accepts any bearer token its upstream API accepts, which is token passthrough. A proxy with a static client ID lets a malicious client ride on a user's earlier consent, the confused deputy problem in the spec's security page. CVE-2025-49596 was also an authentication failure: the Inspector's proxy did not authenticate its own client.
Fix.
- Implement the spec's authorization flow for every HTTP server: protected resource metadata, resource indicators, audience validation and PKCE.
- Validate the token audience on every request and reject anything not issued for this server.
claims = jwt.decode(token, key=jwks, algorithms=["RS256"], audience="https://mcp.example.com/mcp", issuer=TRUSTED_ISSUER)
if "tickets:read" not in claims.get("scope", "").split():
raise Forbidden("insufficient_scope")
- Never forward the client's token upstream. Exchange it, or use the server's own credential for the upstream API, so audit logs show the real actor.
- For proxy servers, obtain per client consent before forwarding to a third party authorization server, as the security best practices page describes.
- Bind local HTTP servers to
127.0.0.1, validateOrigin, and require auth even on localhost.
Our post on MCP server authentication and authorization shows full flows.
MCP08: Lack of Audit and Telemetry
What it is. MCP servers and agents produce too little telemetry to investigate an incident: you cannot say which agent called which tool, with which arguments, on whose behalf.
How it happens. Some hosts keep conversation history only on the user's machine, servers often log HTTP access lines without tool names or arguments, and token passthrough makes upstream logs show the user rather than the agent. After the postmark-mcp disclosure, the obvious question for any affected team was which emails went through it, and that needs a log of tool calls to answer.
Fix. Log every tool call as a structured event, with secrets redacted, to a store your security team can query and agents cannot modify.
{
"ts": "2026-11-02T09:14:22Z",
"session_id": "b7e1",
"user": "alice@example.com",
"client": "example-host/1.4.0",
"server": "tickets@2.3.1",
"tool": "search_tickets",
"arguments_hash": "sha256:9f2c",
"result_bytes": 412,
"decision": "allowed",
"approved_by": "auto:read-only-policy"
}
Record approvals and denials too, and keep tool definition fingerprints so you can show what the model was told at the time. Our MCP telemetry and data governance post covers retention and privacy.
MCP09: Shadow MCP Servers
What it is. Unapproved MCP servers running outside security governance, often with default credentials, permissive configuration or no authentication.
How it happens. A data scientist adds a database server to an IDE; a team leaves a demo HTTP server running; a developer imports a config file from a blog post. None of these appear in the asset inventory, and each can hold production credentials.
Fix.
- Build an inventory. Collect MCP configuration files from managed endpoints (host config files list every server and command) and scan internal networks for MCP endpoints.
- Publish an approved catalog, ideally a private registry that mirrors vetted entries from the official MCP Registry, and make approved servers easier to install than unapproved ones.
- Use host policy where your assistant supports it to restrict connections to approved servers.
- Treat a new server like a new SaaS vendor: an owner, a data classification and a review date.
See our shadow MCP server detection and MCP inventory program posts.
MCP10: Context Injection and Over-Sharing
What it is. Context, the working memory of prompts, retrieved data and intermediate outputs, is shared, persistent or badly scoped, so information from one task, user or agent leaks to another.
How it happens. A multi tenant server caches results by query text rather than by tenant and user. An agent keeps one long lived context, so a document loaded for one customer is still there for the next. A tool returns whole records when the task needed two fields, and the extra fields flow into a later reply.
Fix.
- Key every cache, session and memory store by tenant and user, and test it with two accounts.
- Return the minimum. Design tool output schemas (the spec supports
outputSchema) to carry only the fields the task needs. - Expire context. Clear agent memory between tasks and between users, and do not let resources persist across sessions by default.
- Classify data at the server. If a tool can return regulated data, mark it and let host policy stop that data from reaching tools that send externally.
Our multi tenant isolation post has test cases.
Best practices checklist
| Area | Practice | OWASP items |
|---|---|---|
| Credentials | One scoped, short lived credential per server; OAuth for remote servers; redaction in logs | MCP01, MCP07 |
| Permissions | Per tool scopes, step up auth, separate read and write servers, quarterly review | MCP02 |
| Tool definitions | Show full descriptions, pin and fingerprint them, block on change | MCP03 |
| Supply chain | Pin versions, verify registry namespace, install through a package firewall, SBOM per server | MCP04 |
| Execution | No shell, argument arrays, option and path checks, sandboxed process | MCP05 |
| Untrusted content | Break the trifecta per session, approval for side effects, inspect tool output | MCP06, MCP03 |
| Transport | HTTPS for remote, 127.0.0.1 and Origin checks for local, auth everywhere | MCP07 |
| Visibility | Structured, tamper resistant tool call logs with approvals and fingerprints | MCP08 |
| Governance | Inventory, approved catalog, host allowlists, owners | MCP09 |
| Data | Tenant keyed context, minimal output schemas, context expiry | MCP10 |
Common mistakes
- Trusting
readOnlyHint. It is a hint from the server. A malicious server can mark a destructive tool as read only. - Approving a server once, forever. Tool descriptions and package versions change. Approval should be tied to a fingerprint and a version.
- Running stdio servers from a loaded shell. Every environment variable is available to the server process.
- Treating local as safe. DNS rebinding and CVE-2025-49596 both reached local services from a browser.
- Adding one more tool to a working agent. The third leg of the trifecta is usually added last, by someone who did not set up the first two.
FAQ
Is MCP itself insecure?
No more than HTTP is. The specification includes OAuth 2.1 authorization, Origin validation, explicit bans on token passthrough and a security best practices page. Most incidents come from implementations and from how agents combine servers.
What is the difference between a local and a remote MCP server?
A local server runs on your machine over stdio with your user's privileges. A remote server runs elsewhere over Streamable HTTP and authenticates with OAuth. Local servers risk your laptop; remote servers risk the data their tokens reach.
Does the OWASP MCP Top 10 replace the OWASP Top 10 for LLM applications?
No. The LLM list covers model level risks such as prompt injection in general. The MCP list focuses on the protocol layer: tokens, tools, servers, transports and governance. Use both.
How do I know an MCP server in the registry is genuine?
Check the reverse DNS namespace, which the official registry ties to a verified domain or GitHub account, then check the repository, the package publisher and the version history. Our registry walkthrough for Safeguard's own server shows the steps.
Where should I start with an enterprise rollout?
Inventory first, then an approved catalog, then logging. Our enterprise MCP rollout use case lays out the order.
Where Safeguard fits
Safeguard does not replace the protocol level controls above; they belong in your hosts and servers. It helps with the parts that are supply chain and governance problems.
Vetting a server before you connect it. Gold Open Source is free, needs no login, and gives CVE intelligence for open source packages with affected and fixed versions. Before you pin a server version, look up the server package and its key dependencies there. Our vetting guide shows the flow.
Blocking bad installs. Package Firewall runs inline at install time and blocks known malicious, typosquatted and dependency confusion packages, with allow, warn and block outcomes and every decision recorded to an audit trail. That maps to MCP04 for servers installed through npm and pip.
Inspecting tool output. AI Gateway is a runtime guardrail that sits between your application and the model provider as an inline proxy or SDK shim. Its docs note that indirect injection matters because retrieved content and tool output are untrusted, so the gateway inspects them, not only the human's input. It runs in monitor or enforce mode and is designed to fail open so your application never breaks. That maps to MCP06 and MCP03.
A server that practices least privilege. Safeguard's own MCP server is listed in the official MCP Registry as sh.safeguard/safeguard, with a Streamable HTTP endpoint at mcp.safeguard.sh. Its tool gating gives each tenant a small default set of read tools, keeps the rest off until an administrator enables them per tool, and returns a normal tool error rather than a hidden failure when a disabled tool is called. You can connect it to Claude from the connector directory as the Claude setup page describes.
Tracking what you run. Safeguard scans repositories and container images and produces SBOMs, so the MCP servers you package get the same dependency tracking as your applications. For unknown flaws in agents, servers and skills, see zero day risk in AI agents, MCP servers and skills. For the skills that teach an agent how to use an MCP server, and how to write and lock those down, see our companion guide how to write and secure Agent Skills.
Safeguard is SOC 2 Type II compliant; the report is available on request through the Trust Center. See safeguard.sh/pricing for current plans.
For a longer video treatment of the same ground, the freeCodeCamp MCP Security Fundamentals course by Dan Barahona is recommended viewing.
MCP servers are open source dependencies with credentials attached, and the packages they pull in get vulnerabilities like any other code. When one of them does, the question is how fast a fixed version reaches every place that server runs.
Fix it without waiting for upstream
Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.
1. Autonomous remediation: pull requests, not tickets
When a fixed version exists, Griffin, Safeguard's security model, opens a policy gated pull request that moves the affected dependency to the safe version, and your own CI runs your tests on it before anyone merges. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation categories include dependencies, licenses, security posture and findings, and each one is set separately. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Auto-merge is a separate switch and stays off by default, so your team decides what merges on its own and what still needs a human approval.
2. Gold Open Source: the safe version in seconds, free
gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds.
3. Gold Registry: start from components that are already fixed
Safeguard Gold rebuilds patched versions of open-source packages and images from source, signs each build, attaches an SBOM, SLSA provenance and VEX, and serves them from registry.safeguard.sh to your organization. Every Gold artifact is held continuously to zero exploitable critical or high CVEs and a completed license check, with signed build provenance (signed with Safeguard's key and verifiable with cosign, SLSA Build Level 3 as the design target) and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.
When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened build of a specific package. Ask which packages it covers before you plan around it.
| Your situation | What to use |
|---|---|
| A fixed version exists | Autonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version |
| You need to know today whether you are affected | Gold Open Source lookup and "Am I affected?" checker, free and without login |
| You want vulnerable versions to stop shipping | Safeguard policies and gates in CI |
| Upstream has no fix yet, or will not ship one | A custom Gold request; fork and patch |
| You want to start clean on new projects | Pull hardened components and images from Gold Registry |
One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.
See how Safeguard would help
The first demo connects Claude to the Safeguard MCP server with OAuth and shows how you choose which tools Claude can use. It is the same least privilege pattern this post recommends for MCP02 and MCP07: authorize through OAuth, then enable only the tools the task needs.
Click through it at your own pace, or open it full screen.
The second demo is a real Safeguard CLI session: install, ask for a fix to a vulnerable dependency, approve each step, then scan. If an MCP server you run carries a vulnerable package, this is the flow for fixing it locally with your approval at each step.
Click through it at your own pace, or open it full screen.
To try it yourself, start at safeguard.sh/download and the CLI docs.
Quick check
An agent can read your private repositories, read public issues, and open pull requests. Which OWASP MCP risk does that combination set up, and what is the simplest fix?
It is the lethal trifecta, exploited through MCP06 (prompt injection via contextual payloads, also called intent flow subversion). The simplest fix is to stop a session that has read untrusted content from using tools that publish data, or to require human approval for those tools.
A tool is marked readOnlyHint true. Can the host skip confirmation for it?
Only if the server is trusted. The 2025-11-25 tools specification says clients must treat annotations as untrusted unless they come from trusted servers. A malicious server can label a destructive tool as read only.
Your remote MCP server receives a bearer token and forwards it to the upstream API. What is wrong?
That is token passthrough, which the specification forbids. The server must accept only tokens issued for itself, validate the audience, and use its own credential or a token exchange for the upstream call (MCP07, and MCP01 for the token exposure).
Why does pinning an MCP server to an exact version matter, given postmark-mcp?
The malicious code arrived in a later release, version 1.0.16, after earlier versions behaved normally. Hosts that launch servers with unpinned npx pick up a new release automatically, so pinning plus review before upgrades closes that path (MCP04).
What two settings should every locally run Streamable HTTP MCP server have?
Bind to 127.0.0.1 rather than all interfaces, and validate the Origin header on every connection, returning 403 when it is invalid. Both come from the transports specification and defend against DNS rebinding. Authentication is still recommended on top.
Sources
- OWASP, MCP Top 10 project page and GitHub repository, fetched 2 November 2026.
- Model Context Protocol, Specification 2025-11-25: Architecture, Lifecycle, Transports, Authorization, Security Best Practices, Tools, Elicitation.
- Simon Willison, The lethal trifecta for AI agents, 16 June 2025.
- Postmark, Information regarding malicious "postmark-mcp" package, 25 September 2025.
- npm registry, postmark-mcp package record.
- Invariant Labs, MCP Security Notification: Tool Poisoning Attacks, 1 April 2025.
- Invariant Labs, GitHub MCP exploited: accessing private repositories via MCP, 26 May 2025.
- General Analysis, Supabase MCP demonstration, July 2025.
- NVD, CVE-2025-6514 and GitHub, GHSA-6xpm-ggf7-wc3p, 9 July 2025.
- NVD, CVE-2025-49596 and GitHub, GHSA-7f8r-222p-6f5g, 13 June 2025.
- NVD, CVE-2025-68143, CVE-2025-68144 and CVE-2025-68145, 17 December 2025.
- MCP Registry, publishing authentication and namespaces, fetched 2 November 2026.
- Official MCP Registry, search results for safeguard, fetched 2 November 2026.
- Safeguard docs, Package Firewall, AI Gateway, MCP tool gating and Claude setup.
Last reviewed: 2 November 2026.