4 Security Vulnerabilities Hiding in Your MCP Server's Tool Schema
Every MCP tool starts as a JSON schema. The schema tells the LLM what parameters to send and what types they should be. But schemas aren’t just documentation — they’re the first (and often only) line of defense between an AI agent and your infrastructure.
When the schema says "type": "string" and nothing else, the LLM can send anything. ../../etc/passwd. ; rm -rf /. http://169.254.169.254/latest/meta-data/. The server receives it, and unless someone wrote custom validation code, it executes.
This isn’t theoretical. In the past year, at least 11 CVEs in MCP servers trace directly back to unconstrained schema parameters. Anthropic’s own Filesystem MCP server. Their Git MCP server. FastMCP. mcp-remote. All exploited through the same pattern: a string parameter with no constraints.
I analyzed 8,216 MCP servers and found 4,512 high-risk input parameters with zero validation constraints. Here are the four vulnerability patterns hiding in tool schemas, what the actual CVE exploits looked like, and exactly how to fix each one.
Try the interactive scanner to check your own schema →
1. Freeform file paths: the path traversal surface
The most common dangerous pattern in MCP schemas:
{
"name": "read_file",
"inputSchema": {
"type": "object",
"properties": {
"path": { "type": "string", "description": "File path to read" }
},
"required": ["path"]
}
}
The schema says “path is a string.” It doesn’t say what kind of string. An agent can send ../../etc/passwd, a symlink pointing to /etc/sudoers, or a path that starts with the allowed directory name but continues into a sibling: /tmp/allowed_dir_sensitive_credentials.
The CVE
This exact pattern caused CVE-2025-53110 (CVSS 7.3) and CVE-2025-53109 (CVSS 8.4) in Anthropic’s own Filesystem MCP Server, discovered by Cymulate.
CVE-2025-53110 exploited a naive startsWith() check. If the allowed directory was /tmp/allowed_dir, the attacker could access /tmp/allowed_dir_sensitive_credentials because the malicious path passes the prefix check. CVE-2025-53109 went further — crafted symlinks bypassed fs.realpath() validation through a flawed error handler, giving full filesystem read/write access.
The attack chain: create a symlink to /etc/sudoers, read it through the MCP tool, escalate privileges. On macOS, attackers could drop malicious Launch Agents for persistent code execution.
The same pattern caused three more CVEs
Anthropic’s Git MCP Server had CVE-2025-68145, CVE-2025-68143, and CVE-2025-68144 — reported by Cyata, fixed December 2025. The --repository flag was supposed to restrict access to a specific path. But the tool schema didn’t validate that repo_path arguments in subsequent calls stayed within that path. And git_init accepted arbitrary filesystem paths with no constraints at all — any directory, including .ssh, could be turned into a git repository.
The worst part: CVE-2025-68144 allowed argument injection through the target parameter in git_diff. By injecting --output=/path/to/file, an attacker could overwrite arbitrary files. As The Register noted: “An attacker who can influence what an AI assistant reads — a malicious README, a poisoned issue description — can weaponize these vulnerabilities without any direct access to the victim’s system.”
What the fix looks like
{
"name": "read_document",
"description": "Read a document from the project directory.",
"inputSchema": {
"type": "object",
"properties": {
"path": {
"type": "string",
"pattern": "^[a-zA-Z0-9][a-zA-Z0-9/_.-]*\\.(md|txt|json)$",
"maxLength": 256,
"description": "Relative path within the project directory. No directory traversal."
}
},
"required": ["path"]
}
}
The pattern regex rejects .., absolute paths, and anything that doesn’t match a known file extension. maxLength prevents buffer-style attacks. The description tells the LLM what to expect — and tells a security auditor what to verify.
In our analysis, 357 servers expose path parameters with no pattern, enum, or format constraint. Every one of them has the same attack surface as the pre-patch Filesystem MCP server.
2. Unconstrained URLs: the SSRF surface
{
"name": "fetch_url",
"inputSchema": {
"type": "object",
"properties": {
"url": { "type": "string" },
"method": { "type": "string" }
},
"required": ["url"]
}
}
A string parameter named url with no validation. The server will make an HTTP request to whatever the agent provides. This is the textbook SSRF pattern — Server-Side Request Forgery — and it’s everywhere in MCP.
The CVEs
CVE-2026-32871 in FastMCP’s OpenAPI provider: the _build_url() method substituted path parameter values into URL templates without URL-encoding. Since urljoin() interprets ../ sequences, an attacker controlling a path parameter could escape the intended API prefix and hit arbitrary backend endpoints — with the MCP provider’s own auth headers attached.
BlueRock Security analyzed over 7,000 MCP servers and found 36.7% had potential SSRF exposure through unbounded URL parameters. Their demo against Microsoft’s MarkItDown MCP server showed how an unconstrained URI parameter could query http://169.254.169.254 — the cloud metadata endpoint — and exfiltrate IAM credentials.
And CVE-2025-6514 in mcp-remote (CVSS 9.6) — the most downloaded MCP utility at 437,000+ installs — allowed OS command injection through an OAuth URL parameter. A malicious server could respond with a crafted authorization_endpoint that triggered arbitrary command execution on the client machine. mcp-remote was featured in integration guides from Cloudflare, Hugging Face, and Auth0.
What the fix looks like
{
"name": "get_api_data",
"description": "Fetch data from pre-approved API endpoints only.",
"inputSchema": {
"type": "object",
"properties": {
"endpoint": {
"type": "string",
"enum": ["/api/v1/users", "/api/v1/orders", "/api/v1/products"]
}
},
"required": ["endpoint"]
}
}
The best fix is an enum — a closed set of allowed values. No arbitrary URLs, no SSRF surface. When an enum isn’t practical, a pattern that enforces HTTPS-only and blocks private IP ranges is the minimum:
"url": {
"type": "string",
"pattern": "^https://[a-zA-Z0-9]",
"format": "uri"
}
We found 831 SSRF-susceptible parameters across the MCP ecosystem. Most are url, endpoint, or webhook parameters with "type": "string" and nothing else.
3. Raw query and command strings: the injection surface
{
"name": "execute_query",
"inputSchema": {
"type": "object",
"properties": {
"query": { "type": "string" },
"database": { "type": "string" }
},
"required": ["query"]
}
}
The query parameter accepts any string. If the server passes it to a database, it’s SQL injection. If it passes it to a shell, it’s command injection. The schema provides zero signal about what’s safe.
Why this matters more than traditional injection
In traditional web apps, injection happens when a human types a malicious string into a form. With MCP, the LLM generates the parameter value. This means an attacker doesn’t need direct access to the input — they just need to influence what the LLM sees.
A malicious README in a GitHub repository. A poisoned issue description. A compromised webpage the agent fetches for context. Any of these can contain prompt injection that instructs the LLM to generate a malicious query string. The LLM follows instructions. The schema has no constraints. The server executes.
This is what Simon Willison calls the Lethal Trifecta: an agent with access to private data, exposure to untrusted content, and the ability to take external action. “If your agent combines these three features, an attacker can easily trick it into accessing your private data and sending it to that attacker… we still don’t know how to 100% reliably prevent this from happening.”
What the fix looks like
Replace freeform strings with constrained alternatives:
{
"name": "search_records",
"inputSchema": {
"type": "object",
"properties": {
"collection": {
"type": "string",
"enum": ["users", "orders", "products"]
},
"field": {
"type": "string",
"enum": ["name", "email", "status", "created_at"]
},
"value": {
"type": "string",
"maxLength": 200,
"pattern": "^[^;'\"]+$"
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 50
}
},
"required": ["collection", "field", "value"]
}
}
The collection and field parameters use enums — no arbitrary table or column names. The value parameter rejects SQL metacharacters via pattern. limit has bounds so an agent can’t request a million rows.
Across the MCP ecosystem, we found 566 SQL injection parameters, 368 command injection parameters, and 740 code injection parameters — all accepting arbitrary strings. Combined with the fact that 92% of MCP servers rely on static credentials or no auth at all, most of these are one prompt injection away from exploitation.
4. Destructive operations with no guardrails
{
"name": "delete_records",
"inputSchema": {
"type": "object",
"properties": {
"table": { "type": "string" },
"where": { "type": "string" }
},
"required": ["table"]
}
}
A tool called delete_records that accepts an arbitrary table name and an arbitrary WHERE clause. No confirmation step. No scope constraint. No maxItems to limit blast radius.
This isn’t a schema vulnerability in the traditional sense — it’s a schema that provides zero defense against misuse. When an agent autonomously calls a delete tool, the schema should constrain what can be deleted and how much.
What the fix looks like
{
"name": "archive_record",
"description": "Soft-delete a single record by ID. Requires confirmation.",
"inputSchema": {
"type": "object",
"properties": {
"collection": {
"type": "string",
"enum": ["orders", "drafts"]
},
"record_id": {
"type": "string",
"pattern": "^[a-zA-Z0-9_-]{1,64}$"
},
"confirm": {
"type": "boolean",
"description": "Must be true to execute the deletion."
}
},
"required": ["collection", "record_id", "confirm"]
}
}
Three changes: the tool name signals intent (archive_record not delete_records), the parameters are scoped to a single record by ID (no bulk WHERE clauses), and a confirm flag forces the agent to make an explicit decision.
What you can do right now
Audit your schemas. Look for string parameters on dangerous operations (file reads, database queries, HTTP requests, shell commands) that have no pattern, enum, format, or maxLength constraint. These are the parameters that show up in CVEs.
Scan your tool definitions with our free browser-based scanner. Paste your tools/list response and get line-by-line annotations showing exactly which parameters create attack surface. Everything runs client-side — nothing leaves your browser.
Use enums wherever possible. An enum is a closed set of allowed values. If a tool only needs to access three API endpoints, don’t accept a URL — list the three endpoints as an enum. This eliminates entire vulnerability classes.
Add patterns to string parameters. "pattern": "^[a-zA-Z0-9/_.-]+$" on a path parameter blocks ../ traversal. "pattern": "^https://" on a URL parameter blocks non-HTTPS and private IPs. These are one-line additions to your schema.
Schema hardening alone isn’t enough
Every vulnerability in this post has a schema-level fix. But schemas are suggestions — the MCP spec doesn’t mandate that servers or clients enforce schema validation. And even a perfectly constrained schema can’t prevent tool poisoning — where malicious instructions are embedded in tool descriptions that the LLM obeys — or rug pull attacks where tool definitions silently change after you approve them.
These are architectural problems. The MCP spec defines no audit primitives, no rate limiting, and only recently added authentication patterns. As Invariant Labs put it after demonstrating data exfiltration through the GitHub MCP server: “This is not a flaw in the GitHub MCP server code itself, but rather a fundamental architectural issue that must be addressed at the agent system level.”
This is why a control plane exists — a layer between the MCP client and server that enforces validation, auth, rate limits, PII detection, and audit logging regardless of what the individual server does. The server doesn’t need to validate. The control plane does.
Scan your schemas → · Read the full security checklist → · Get started free →
Methodology and sources
Tool definitions from ToolSDK MCP Registry (4,548 servers, 8,010 tools). High-risk parameter detection via regex matching on tool names, descriptions, and schema property names. CVE details from NVD, vendor advisories, and original researcher disclosures. BlueRock SSRF statistics from their analysis of 7,000+ MCP servers. Schema examples are simplified for clarity; production schemas should include additional constraints appropriate to their context.
Related reading: 4,500 MCP Servers, Zero Input Validation · MCP Auth Appropriateness Audit · The MCP Audit Gap · I Audited 7,522 AI Agent Skills · OWASP Agentic Top 10