Topic: mcp server security owasp

MCP server security and OWASP — mapping the API Top 10 and LLM Top 10 onto Model Context Protocol

OWASP's two most-cited threat lists for our era — the API Security Top 10 (2023) and the Top 10 for LLM Applications (2025) — both have something to say about MCP servers, and neither is a perfect fit. This is an honest mapping of each category onto the MCP threat surface, with corpus examples from 101 of the most-installed Claude skills and MCP servers and notes on what doesn't translate.

TL;DR

Six of the OWASP API Top 10 categories map cleanly onto MCP server threats — most notably SSRF (API7), Broken Authentication (API2), Security Misconfiguration (API8), and Improper Inventory Management (API9). Three of the OWASP LLM Top 10 — Insecure Plugin Design (LLM07), Excessive Agency (LLM08), and Sensitive Information Disclosure (LLM06) — are essentially describing MCP servers under another name. Three categories from the API list (BOLA, BFLA, Unrestricted Business Flows) translate poorly because MCP doesn't have business-object semantics. The MCP-specific surface that neither list captures cleanly is credential echo into tool response (38% of our corpus), prompt-injection of tool I/O, and permission scope vs. handler implementation drift. Below: each category with definition, MCP-shape, corpus example, prevention, and a coverage signal for SkillAudit's six-axis review.

Why the mapping isn't 1:1

Three observations explain why neither OWASP list maps perfectly onto MCP:

The right way to use OWASP for MCP is as a vocabulary rather than a checklist. The categories give you names to put on findings. The mapping below shows where each name applies cleanly, where it stretches, and where you need MCP-specific language instead.

OWASP API Security Top 10 (2023) → MCP

API1: Broken Object Level Authorization (BOLA)

API definition: server fails to verify the requesting user is authorized for the specific object ID being accessed.

MCP shape: stretches. Most MCP servers don't expose a per-object-ID surface — they expose tool handlers parameterized by free-text arguments. The closest pattern is a tool like read_file({path}) that doesn't validate the path is inside an allow-listed directory. That isn't strictly BOLA in the API sense, but the same threat shape — caller bypassing intended scope by varying the input — applies.

Corpus signal: ~5% of our corpus has a path-traversal-shaped finding in a file-read tool handler. Caught under our security axis.

Prevention: deny-by-default scope. resolve() the path, compare against the allow-list root, reject if it escapes.

API2: Broken Authentication

API definition: attacker can compromise auth tokens, session, or identity.

MCP shape: direct fit, common pattern. MCP servers frequently authenticate to upstream services (GitHub, Cloudflare, Supabase, etc.) using a user-provided token in process.env. Broken-auth findings in this surface are: token logged to stdout/stderr, token echoed in tool responses, token used over plaintext HTTP, token sent on redirect. The redirect case is the deepest one — fetch(${baseUrl}/path) with a static Authorization: Bearer ${env.X} header that follows a 302 to an attacker-controlled host leaks the token. Detail: anatomy of a credential leak.

Corpus signal: 38% credential-handling findings.

Prevention: never read secrets into response paths. Strip Authorization on cross-host redirect (or pin redirect: 'manual'). Document which env vars are secret and don't log them. Static rules catch the easy patterns; LLM-assist catches the dynamic-base case.

API3: Broken Object Property Level Authorization

API definition: over-broad property exposure (mass-assignment-style failures).

MCP shape: stretches. The MCP equivalent is a tool handler that returns more data than the caller asked for — e.g., a get_user({id}) handler that returns the full user record including password_hash. Less common than the API surface because MCP responses are typically free-text, not structured objects, but the pattern shows up in DB-shaped servers.

Corpus signal: ~3%, mostly in DB-wrapper servers.

Prevention: explicit response-shape allowlists. Don't SELECT *; project to a defined column set.

API4: Unrestricted Resource Consumption

API definition: attacker exhausts CPU, memory, bandwidth, or wallet by submitting expensive requests.

MCP shape: direct fit, often overlooked. An MCP server that runs git log with no max-line cap, or fetches a remote URL with no size cap, or invokes an upstream LLM with no token cap, is rate-limited only by the client's politeness. We've seen this turn into wallet-burning behavior on tools that paginate upstream APIs without bounding the page count.

Corpus signal: ~12% of our corpus has at least one unbounded-consumption pattern. Caught under the security axis with a separate sub-finding class.

Prevention: bound every external call (size, timeout, page count). Add an in-process budget. Document the bound so callers can plan.

API5: Broken Function Level Authorization (BFLA)

API definition: attacker invokes admin-only functions because authorization isn't checked at the function level.

MCP shape: doesn't translate. MCP servers don't have an admin-vs-user privilege concept; they run with whatever privileges the host process has. The closest pattern is the permissions-hygiene axis — declared tools that exceed actual usage — but that's a different threat shape.

Corpus signal: n/a; permissions hygiene is its own axis.

Prevention: declare narrowly; if a tool isn't used, don't register it.

API6: Unrestricted Access to Sensitive Business Flows

API definition: automated abuse of legitimate functionality (scraping, signup spam, scalping).

MCP shape: doesn't translate at the protocol layer. The threat exists at the upstream service the MCP server proxies to, not at the MCP server itself. A SkillAudit review will not flag this because the surface isn't ours; it's the upstream's.

Corpus signal: n/a.

Prevention: defense-in-depth in the upstream service.

API7: Server Side Request Forgery (SSRF)

API definition: server fetches a URL controlled by the attacker, reaching internal-network or sensitive-host destinations.

MCP shape: direct fit, the most common high-severity finding in the corpus. Tool handlers like fetch_url({url}) with no allow-list, no IP-block check, and no protocol restriction are textbook SSRF. The dynamic-base variant — fetch(`${baseUrl}/${path}`) where baseUrl comes from an environment variable or a tool argument — is what most default SAST rules miss. The server runs inside the user's network, often inside an agent that has cloud credentials in env, so SSRF is a credentials-stealing primitive too.

Corpus signal: 50% — by far the dominant finding class.

Prevention: URL allow-list, parsed-host comparison (not string prefix), deny RFC 1918 + 127/8 + 169.254.169.254 + IPv6 link-local, deny file:// and gopher:// schemes, manual redirect handling. Detail in best practices.

API8: Security Misconfiguration

API definition: insecure defaults, exposed debug endpoints, missing hardening.

MCP shape: direct fit. MCP servers commonly ship with debug logging on by default, with verbose error messages that echo arguments back, with a missing or permissive CORS config when an HTTP transport is used, with no origin check on SSE endpoints. We've also seen servers that listen on 0.0.0.0 when they should listen on 127.0.0.1.

Corpus signal: ~18%; caught across security and credential axes.

Prevention: production-default flag set. Strip arg values from error messages. Pin transport to localhost when running over HTTP.

API9: Improper Inventory Management

API definition: deprecated/v1/staging endpoints kept exposed; no clear inventory of versions.

MCP shape: direct fit, manifests as protocol-version drift and unmaintained branches. A server that supports MCP protocol 0.6 with a v1 branch still active and unpatched is the typical shape. Archived repositories are the strongest signal here.

Corpus signal: 9 of 101 corpus servers were archived; covered under the maintenance axis. Detail.

Prevention: tag releases, advisory-feed registration, deprecation notices on old protocol versions, archived-repo signal in package.json.

API10: Unsafe Consumption of APIs

API definition: the API trusts upstream APIs more than it should — accepting their responses without validation.

MCP shape: direct fit, frequently underweighted. MCP servers fetch from upstream services and pass the responses to the LLM. If the upstream is compromised or malicious, its response becomes a prompt-injection vector against the agent calling the MCP server. A web-search MCP that returns raw HTML is a textbook example.

Corpus signal: ~22%, partially covered by our prompt-injection axis (LLM-assisted probe).

Prevention: response-shape validation, sanitization of user-visible upstream content, awareness that "the upstream is trusted" is rarely true at LLM scale. Detail: testing the prompt-injection axis.

OWASP Top 10 for LLM Applications (2025) → MCP

LLM01: Prompt Injection

LLM definition: user-controlled or upstream-controlled text is interpreted as instructions by the LLM.

MCP shape: direct fit, two distinct surfaces. Direct injection: a tool handler that accepts a free-text argument and feeds it back to the LLM unchanged. Indirect injection: a tool handler that fetches data from an upstream (web, GitHub, S3) and returns the upstream's content to the LLM verbatim. The indirect case is harder and more common; an attacker who can post to a public forum can sometimes drive an agent's behavior via a search MCP that scrapes that forum.

Corpus signal: we LLM-probe every server in the corpus; ~30% have at least one prompt-injection finding under our methodology.

Prevention: structured-output formatting (return JSON, not free text, where possible), source-attribution tagging in tool responses, defense-in-depth at the agent's system prompt. Detail: engine v0.3 calibration delta.

LLM02: Insecure Output Handling

LLM definition: downstream consumer trusts LLM output without validation.

MCP shape: partial fit, more about the agent than the MCP server. The MCP-side responsibility is to clearly mark untrusted-source content; the agent-side responsibility is to not execute it as code. We touch this under the documentation-completeness axis (servers should document which response fields contain untrusted content).

Corpus signal: ~5% of our corpus has any explicit untrusted-content marking.

Prevention: tag response sections with source: "external" or equivalent. Document trust boundaries.

LLM03: Training Data Poisoning

LLM definition: attacker poisons the training corpus.

MCP shape: doesn't translate. MCP servers don't influence training. We don't review for this.

LLM04: Model Denial of Service

LLM definition: attacker exhausts model context window or token budget.

MCP shape: partial fit. Overlaps with API4 (Unrestricted Resource Consumption). An MCP server that returns 200KB of HTML on a single tool call can fill the agent's context window and DoS the calling agent. Bounded.

Corpus signal: covered under API4 above.

Prevention: response-size cap. Pagination with explicit budget.

LLM05: Supply Chain Vulnerabilities

LLM definition: dependency CVEs, model provenance, plugin compromise.

MCP shape: direct fit, but mostly orthogonal to MCP-specific review. We surface dependency-CVE signal under the maintenance axis. SCA tools (Dependabot, Snyk, OSV-Scanner) are the right primary tool here; SkillAudit is a complement, not a replacement. Detail: tools landscape.

Corpus signal: ~8% of our corpus had at least one open dependency-CVE at audit time.

Prevention: SCA in CI. Pin major versions. Subscribe to advisories.

LLM06: Sensitive Information Disclosure

LLM definition: the LLM (or its plugins) leaks sensitive data — secrets, PII, internal addresses.

MCP shape: direct fit, this is our credential-exposure axis. The most common pattern is environment-variable secrets read into tool responses (38% of corpus). Tied to the API2 broken-auth case above. Deepest illustration: the redirect-leaks-token pattern in anatomy of a credential leak.

Corpus signal: 38% as cited; the dominant credential class.

Prevention: never read secrets into response paths; strip Authorization on cross-host redirect; PII filter on response if applicable.

LLM07: Insecure Plugin Design

LLM definition: plugins (read: MCP servers) accept untrusted input as parameters and lack input validation, output validation, or authorization.

MCP shape: this is the MCP server review. LLM07 is essentially the OWASP shorthand for "audit your MCP server." The whole six-axis surface — security, permissions, credentials, maintenance, compatibility, docs — is what closing LLM07 looks like in practice.

Corpus signal: 42% of corpus earned an F under our methodology, which is roughly the LLM07 fail rate at the population level.

Prevention: the rest of this page.

LLM08: Excessive Agency

LLM definition: the LLM has more permissions than the task requires.

MCP shape: direct fit, our permissions-hygiene axis. Manifests as: registered tools that are never invoked; tool descriptions that read more like "do anything" than "do this specific thing"; servers that take a single broad token (e.g., a GitHub token with repo scope) when narrower scopes would do.

Corpus signal: ~25% of corpus has at least one over-broad-scope finding.

Prevention: declare narrowly. Use the narrowest upstream scope. Diff declared vs. used and remove the gap.

LLM09: Overreliance

LLM definition: users trust LLM output without verification.

MCP shape: doesn't translate at the server layer. It's a UX and governance concern at the agent layer. We don't review for this.

LLM10: Model Theft

LLM definition: attacker exfiltrates model weights or extracts proprietary models via querying.

MCP shape: doesn't translate. MCP servers don't host models.

The MCP-specific surface that neither list captures

Three threat shapes are central to MCP review and don't have a clean home in either OWASP list:

Calling these out under their own names — rather than forcing them into LLM07 or API8 — is part of why the MCP-aware review category exists at all. Our methodology documents each as a first-class axis.

Coverage matrix: OWASP categories vs. SkillAudit axes

OWASP categoryMaps to MCP?SkillAudit axisCorpus rate
API1 BOLAStretchesSecurity (path traversal)~5%
API2 Broken AuthenticationDirectCredential exposure38%
API3 Property-Level AuthStretchesSecurity (DB-shape)~3%
API4 Unrestricted Resource ConsumptionDirectSecurity (DoS sub-class)~12%
API5 BFLANon/a
API6 Sensitive Business FlowsNo (upstream)n/a
API7 SSRFDirectSecurity (primary)50%
API8 Security MisconfigurationDirectSecurity + credential~18%
API9 Improper InventoryDirectMaintenance~9%
API10 Unsafe API ConsumptionDirectPrompt-injection probe~22%
LLM01 Prompt InjectionDirectPrompt-injection probe~30%
LLM02 Insecure Output HandlingPartialDocumentation~5% marked
LLM03 Training Data PoisoningNon/a
LLM04 Model DoSPartialSecurity (DoS sub-class)covered by API4
LLM05 Supply ChainDirectMaintenance~8%
LLM06 Sensitive Info DisclosureDirectCredential exposure38%
LLM07 Insecure Plugin DesignDirect (this IS the review)All six axes42% F-grade
LLM08 Excessive AgencyDirectPermissions hygiene~25%
LLM09 OverrelianceNo (agent layer)n/a
LLM10 Model TheftNon/a

"Corpus rate" = share of the 101-server corpus showing at least one finding in this class under SkillAudit's methodology v0.3. See the corpus paper for full distributions.

How to use this on your own server

  1. Skim the table. The "Direct" rows are where OWASP's vocabulary fits cleanly onto your MCP server. Use those names in your README's security section, your incident-response playbook, and your release notes — security buyers recognize them.
  2. Run an audit. SkillAudit's six-axis review covers all the "Direct" rows automatically and produces an A–F grade plus per-axis findings. Free tier covers public repos. Run one on your own GitHub URL.
  3. Fix the highest-rate classes first. If you have an SSRF, fix it before anything else — half the F-grades in the corpus are SSRF-driven. Credential-echo next. Resource-consumption third.
  4. Write a security policy that references both lists. Map each category to a control objective in your own framework. The Team plan's policy export does this for you, mapped to the OWASP categories above. Detail: install-gate policy.

Run an audit

Related questions

Is there an OWASP-published MCP-specific top 10 yet?

Not as of mid-2026. The OWASP GenAI working group is the closest active body; their LLM Top 10 (2025) explicitly addresses plugins under LLM07, which is the nearest official concept. We expect MCP-specific guidance to land in a future revision; until then, this mapping is what we use internally.

Should I just rely on OWASP's API Top 10 and skip the MCP-specific work?

The API Top 10 is necessary but not sufficient. Six of its categories map cleanly, but the credential-echo, prompt-injection-of-tool-I/O, and permissions-drift threats are MCP-specific and won't show up in an API-shaped review. A reviewer who only ran OWASP API would have given several F-grade servers in our corpus a pass.

Why do you LLM-probe for prompt injection? Isn't that better caught statically?

Static analysis can flag obvious patterns (free-text passthrough, raw HTML in responses) but cannot prove the absence of an injection vector. The LLM-assisted probe is honest about this — it tries known injection patterns against the actual tool surface and reports what landed. Detail: calibration delta.

Does running OWASP ZAP or Burp on an MCP server help?

Marginally. ZAP and Burp expect HTTP API shapes. MCP runs over stdio or SSE typically; even when over HTTP, the surface is JSON-RPC tool calls, not REST endpoints. You can wrap an MCP server in an HTTP transport and probe it, but you'll mostly catch transport-layer findings (CORS, headers) — not the tool-handler classes that dominate our corpus.

Where can I cite OWASP from in my own MCP server's README?

The two stable URLs are OWASP API Security Top 10 (2023) and OWASP Top 10 for LLM Applications. Reference LLM07 specifically when discussing your security posture; it's the term reviewers and buyers will recognize fastest in MCP context.

Further reading