Safeguard
AI Security

Securing MCP Servers for AI Agents

Five CVEs in 2025 alone trace MCP tool compromise back to one root cause: unsanitized strings piped into exec(). Here's how to expose and consume MCP safely.

Safeguard Research Team
Research
7 min read

The Model Context Protocol turned one year old in November 2025, and its authorization spec changed twice in that time — most recently on November 25, 2025, when MCP servers were formally redefined as OAuth 2.1 resource servers rather than ad hoc bearer-token endpoints. That revision mattered because the gap it closed was already being exploited in practice: by mid-2025, the GitHub Advisory Database had logged at least five distinct CVEs in production MCP server implementations, every one of them a command injection bug caused by passing LLM-supplied tool arguments straight into a shell. CVE-2025-53107 in the @cyanheads/git-mcp-server package, CVE-2025-53818 in a GitHub Kanban MCP server, and CVE-2025-53355 in MCP Server Kubernetes all trace to the same root cause — child_process.exec or execSync called on a string built from untrusted model output. MCP's core design problem is that a tool call looks like a function invocation but behaves like a network request from an untrusted client: the LLM, not a developer, decides what arguments to send. This post covers the three places that trust boundary needs enforcement — server-side auth, scoped tool permissions, and output validation.

Why does MCP authorization need its own OAuth profile instead of a plain API key?

MCP authorization needs its own profile because a bare API key can't express who is calling on whose behalf, or which resource the token is valid for. The specification's basic authorization document (updated 2025-11-25) requires MCP servers to act as OAuth 2.1 resource servers and clients to act as OAuth 2.1 clients, with the server exposing standard OAuth 2.0 or OIDC discovery metadata so a client can find the right authorization server automatically. Earlier 2025 revisions added resource indicators so an access token is explicitly audience-restricted to one MCP server, closing a token-passthrough hole where a token issued for one downstream API could be replayed against another, as Auth0's engineering team documented in its writeup of the spec changes. The "confused deputy" pattern maps directly onto MCP: without an audience-bound token, a compromised or malicious MCP server can forward a user's credential to a different API than the one the user consented to. Treating the MCP server as a first-class resource server, not just a wrapper, is what makes token scope enforceable at all.

What changed in the November 2025 MCP spec update, and why does it matter for enterprises?

The November 2025 update added two mechanisms aimed squarely at enterprise deployment friction: Client ID Metadata Documents (CIMD) and Enterprise-Managed Authorization. CIMD lets an MCP client self-describe by pointing to a URL it controls instead of pre-registering with every authorization server it might talk to — useful when a single AI agent needs to reach dozens of independently-run MCP servers, a registration burden that didn't scale under the prior model, as developer Aaron Parecki noted in his analysis of the change. Enterprise-Managed Authorization, built on the Cross App Access (XAA) pattern, lets a company's own identity provider mint MCP access tokens through token exchange without a user-facing OAuth redirect — meaning IT can centrally govern which employees' agents can reach which MCP servers, rather than each employee individually authorizing every connector. For a security team, this is the difference between "any employee can paste a bearer token into Claude Desktop" and "access is provisioned and revocable through the existing IdP," which is the control most SOC 2 and enterprise procurement reviews will ask for first.

What is the most common vulnerability class in real MCP server implementations?

The most common vulnerability class, by a wide margin, is OS command injection from unsanitized tool arguments reaching a shell. CVE-2025-53107 (git-mcp-server), CVE-2025-53818 (GitHub Kanban MCP server's add_comment tool shelling out to the gh CLI), CVE-2025-53355 (MCP Server Kubernetes, across multiple tool handlers using execSync), and CVE-2025-59834 (adb-mcp's executeAdbCommand() building shell strings from tool arguments) all share the identical defect: a tool handler concatenates model-supplied text into a command string instead of passing arguments as an array to execFile/spawn. A fifth, CVE-2025-6514, hit mcp-remote itself — the client library — with command injection triggered simply by connecting to a malicious remote MCP server, showing the risk runs both directions. The fix is not novel; it's the same argv-array discipline that has prevented shell injection in web backends for two decades. What's new is that the "attacker-controlled input" is now often an LLM's own generated text, which developers are less conditioned to treat as hostile than a raw HTTP request body.

How should you scope tool permissions when exposing an MCP server?

Scope tool permissions the same way you'd scope an API token issued to a semi-trusted third party — least privilege per tool, not one blanket credential for the whole server. Bind each issued token to the specific resource-indicator audience for that MCP server (per the November 2025 spec) so a leaked token can't be replayed elsewhere, and where the server exposes both read tools (list findings, get SBOM) and write/mutating tools (fix vulnerability, merge PR), issue separate scopes so a compromised read-only agent session can't invoke destructive actions. Safeguard's own hosted MCP server illustrates one version of this pattern in practice: it authenticates over bearer tokens scoped to a sg_api_ key tied to a tenant, and enterprises that need the tool surface to never leave their network can deploy the same tool set via a self-hosted Helm chart inside their own VPC rather than calling the multi-tenant hosted endpoint. Whichever model you choose, the operative question for every tool you expose is: "if this exact tool call came from a prompt-injected or hijacked agent session, what's the blast radius?"

Why can't you trust the data an MCP tool call returns?

You can't trust a tool call's return value because it re-enters the model's context as if it were ground truth, and an attacker who controls any upstream data source the tool touches can inject instructions there. This is the confused-deputy risk running in the other direction: a "read GitHub issue" tool that faithfully returns issue text is also faithfully returning any hidden instruction an attacker planted in that issue body, and a model with no boundary between "data" and "instructions" may act on it. Treat every tool output the way you'd treat unsanitized HTML from a third-party site — validate its shape against an expected schema, strip or flag content that resembles directives to the model, and never let a tool's output silently expand what the agent is authorized to do next without a fresh permission check. Pair that with the command-injection lesson above and the pattern is consistent: every MCP boundary crossing, in either direction, needs to be handled as untrusted input, not as a trusted internal function call.

How Safeguard Helps

Safeguard's AI security posture work extends the same reachability-first approach it applies to Python and JavaScript dependencies to the MCP layer: scanning MCP server source for the exec/execSync injection pattern that produced CVE-2025-53107, CVE-2025-53818, and CVE-2025-53355, and flagging tool handlers that pass unvalidated model input into a shell before they ship. Safeguard's own hosted MCP server is deployed against that same standard — Bearer-token auth scoped to a tenant's sg_api_ key, with a self-hosted Helm chart option for teams that need the tool surface running entirely inside their own VPC rather than trusting a shared multi-tenant endpoint. For teams building their own MCP integrations, that means the audit questions in this post — is every tool argument shell-escaped, is every token audience-bound, is every tool output schema-validated before it re-enters model context — are exactly the checks worth automating before an agent gets production write access to your systems.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.