Securva · Original ResearchDraft for review
The MCP Security
Top 10.
The ten ways Model Context Protocol servers, SDKs, and AI-agent clients actually get broken. Not theory, compiled from a hands-on cross-ecosystem audit and real published CVEs. Each risk comes with the "incomplete-fix tell" we use to find it, and the fix.
Why this exists, and why it's ours: as AI agents and MCP go mainstream, nobody has written the canonical security checklist for them. We have, because we found the bugs first: published CVEs in widely-used software plus an audit that mapped this exact class across the official Go, .NET, and Swift MCP SDKs, Zed, and VS Code. Receipts, not opinions.
01
OAuth credential leak on cross-origin redirectHigh-impact
What it is
An MCP client's OAuth token / refresh / registration request follows a redirect from the token endpoint using a default auto-redirecting HTTP client with no same-origin clamp. On a cross-origin 307/308 the request body (client_secret, PKCE code_verifier, refresh_token, even an enterprise ID token) is re-POSTed to the attacker's origin.
The incomplete-fix tell
A client that clamps redirects on the discovery request but not the credential-bearing token POST (internal inconsistency). Or a fix that ports to one language's SDK but not the others (cross-language parity gap).
Receipts
Mapped across the official Go, .NET, and Swift SDKs + mark3labs/mcp-go + Zed + VS Code (reported). Parent fixes: Python GHSA-qx49, TS PRs #2901/#2902. Rust & Ruby SDKs show the correct safe-by-default pattern.
Fix
Route credential-bearing OAuth POSTs through a no-redirect (or same-origin-only) HTTP client. Mirror the Rust SDK's stop_redirects / Ruby's origin-guard.
02
Server-chosen authorization server / unbound credentialsCritical
What it is
The client sends OAuth credentials (especially a configured confidential client_secret) to a token endpoint the untrusted MCP server chose via discovery, with no binding of the credential to its expected issuer. A malicious server harvests the secret directly, no redirect needed.
The incomplete-fix tell
Issuer self-consistency (metadata issuer == fetched URL) is NOT the defense. The real fix binds the configured credential to its expected AS and refuses a server-chosen AS change.
Receipts
Parent: Python SDK GHSA-qx49-fqc8-xw99 (High). Official Go & .NET SDKs correctly defend it (SEP-2352 binding); the audit confirmed which clients do and don't.
Fix
Bind every configured credential to its expected authorization server; reject a server-initiated AS switch for confidential clients.
03
SSRF via attacker-influenced fetch URLCritical
What it is
A server or gateway fetches a URL the caller (or an upstream token/discovery doc) controls, with no scheme/host allowlist and/or redirect-following, reaching internal services and cloud metadata (169.254.169.254).
The incomplete-fix tell
A fix that guards one sub-request (ping/realm/auth) but leaves the data-plane fetch (manifest/blob/content) or the redirect unguarded. Validate-by-name then fetch-by-name with no IP pin = DNS-rebinding TOCTOU. IPv4-only resolution = IPv6/AAAA bypass.
Receipts
IBM mcp-context-forge SSE health-check redirect SSRF; docker/model-runner registry-fetch SSRF-to-IMDS (both incomplete-fixes of prior guards).
Fix
Allowlist scheme + host, pin the resolved IP across validate-and-fetch, block private/link-local ranges on both IPv4 and IPv6, and don't follow redirects on server-side fetches.
04
JWKS fetch amplification + JKU/X5U SSRFModerate
What it is
A JWKS client re-fetches remote keys on an attacker-controlled unknown kid (parsed before signature verification, so unauthenticated) with no cooldown or negative cache, an amplification DoS. The JKU/X5U variant takes the key URL from an attacker token header with no allowlist (SSRF).
The incomplete-fix tell
A cooldown keyed on a per-kid cache with no global throttle is still vulnerable (rotate the kid). A global-timestamp cooldown mitigates it.
Receipts
PyJWT CVE-2026-101917 (ours) + the CVE-2026-48524 family; confirmed residuals in Authlib & get-jwks; safe exemplars in panva/jose & jwx.
Fix
Global rate-limit + negative-cache unknown-kid refetches; allowlist any header-supplied JWKS URL.
05
Unauthenticated tool-dispatch to exec / file sinks (RCE)Critical
What it is
A server exposes a tool that reaches a command / eval / file read-write sink, dispatchable with no auth (or before the auth/approval gate) on the released version and default config.
The incomplete-fix tell
Read the sink to the metal: host exec vs a forwarded/gated sandbox (not the same risk). Confirm the sink ships in a release, not just main. Check auth-transport parity (an auth that exists on stdio but got dropped on the HTTP transport).
Fix
Authenticate the transport, gate tool dispatch before any sink, and never wire a raw host-exec/file sink to an unauthenticated tool.
06
Broken auth / scope / tenant isolation (BOLA)High
What it is
A tool or route is reachable cross-tenant / cross-user with no ownership check, or a scope gate that a sibling helper skips.
The incomplete-fix tell
Enumerate all gate-helper names first, a sibling like userHasGroupPermission won't contain the substring you grepped. A lone narrow authz fix has higher residual odds than a batch sweep.
Fix
Centralize the ownership/scope check; make every route and helper go through it.
07
Tool-poisoning & indirect prompt injectionHigh
What it is
Tool descriptions, metadata, or results are attacker-controllable and flow into the model as instructions, steering the agent into calling a tool with attacker-chosen arguments (e.g. a malicious backend URL). The realistic MCP attacker needs no network access, just a poisoned document or record the agent later reads.
Fix
Treat every tool argument and header as attacker-controllable in an agent context; don't ingest untrusted tool metadata as trusted instructions; isolate and label tool output.
08
Secret leakage into tool output, logs, or model contextModerate
What it is
Tokens/keys get serialized into tool output, logs, or the model's context (where a prompt-injection can then exfiltrate them).
The incomplete-fix tell
A serialization fix is complete only if every serializer routes through the redacting DTO, a left-intact struct JSON tag after a secret-exposure fix is a sibling-serializer gap.
Fix
Redact at a single serialization boundary; never place raw credentials into model-visible context.
09
Insecure defaults & DNS-rebinding / transport mismatchHigh
What it is
An HTTP/SSE MCP server ships open-by-default, bound to 0.0.0.0 with an empty/weak token and auto-approve on, or lacks Origin/Host validation (DNS-rebinding), or carries auth on stdio but not the HTTP transport.
The incomplete-fix tell
A "documented open-by-default" repeatedly declared intentional is a by-design dead end, but an undocumented weak default + a missing rebinding guard is a real finding.
Fix
Secure defaults (loopback bind, required token, no auto-approve), validate Origin/Host, and keep auth parity across every transport.
10
Path traversal in file-handling toolsModerate
What it is
A tool that reads or writes files builds the path from attacker input (a tool argument, a filename in a response) with only a lexical check, allowing escape outside the intended directory.
The incomplete-fix tell
Read the guard to the metal: a tagError()/assert() that looks log-only may actually throw (type : never) and be safe, while a named "validate" may only log. Confirm halt-vs-continue by control flow, not by the function's name. Watch for the symlink-escape the resolver's own docs wave off.
Receipts
Audited across figma-developer-mcp and the reference filesystem servers (guards found complete, the method confirmed).
Fix
Canonicalize + contain to an allowed root (and handle symlinks), on every path-building tool.
How to use this
Building an MCP server, SDK, or agent? Run every item against your code. Shipping an AI product and need it checked? That is exactly the audit Securva does, we wrote the list because we found the bugs. Each risk here maps to a real finding, a detection rule, and a one-line fix. Measured, reproduced, fixable.