Blog
>
Claimed, Not Enforced: The MCP Security Gap

Claimed, Not Enforced: The MCP Security Gap

From broken authorization to skipped confirmations, our MCP research uncovered one recurring problem: the control isn’t where the action happens.

Parth Shukla

We audited 30+ MCP servers, gateways, clients and frameworks with roughly 200 million downloads in the past 30 days. One pattern dominates: an MCP server advertises a restriction it does not actually enforce. One finding is already CVE-2026-79746 (CVSS 8.1). Here is the gap, pattern by pattern, with the code that breaks, the weakness class, and the fix.

An MCP server is usually a thin layer of glue. A model asks it to do something, and the server turns that request into something real on the other side: a database query, a shell command, an API call that spends your credentials, or a file written to disk. 

We spent months auditing the public MCP server ecosystem from the outside in. We discovered and filed security issues across highly popular 30+ widely used MCP servers, gateways, clients and packages. Several of them were rated high severity. To put things in perspective, these projects saw roughly 200 million downloads in the last 30 days, the top one being an MCP server framework which has been downloaded ~99 million times. A flaw in the framework has much higher impact than an application, it impacts everything built on top of it.

Almost every serious bug comes down to the same gap: the server claims a restriction, but the restriction does not sit on the path the request actually takes. Our first finding from this study is already a published CVE, CVE-2026-79746 (CVSS 8.1 - https://github.com/samanhappy/mcphub/security/advisories/GHSA-454m-4vm6-842f) and the study has since produced a run of coordinated disclosures ranging up to one-click remote code execution in widely used clients. The patterns below run in that order, from the quietest config mistake to a hostile server that runs code on the user's own machine. Each comes with the mechanism, the weakness class, and the fix.

Important Note

Every issue behind this post went to the vendor first, through HackerOne, Bugcrowd, or a GitHub Security Advisory, and nothing which is still under private coordination is named here.

1. The gate that only guards the front door
The vulnerability

This vulnerability was discovered in mcphub, which is a centralized gateway to manage and scale multiple MCP servers by organizing them into HTTP endpoints, thereby supporting access to all servers, individual servers, or logical server groups. 

The mcphub gateway is the clearest public example of missing enforcement of claimed restrictions vulnerability (GHSA-454m-4vm6-842f, CVE-2026-79746, CVSS 8.1; affected < 1.0.31, fixed in 1.0.31). 

It lets you mint an access key scoped to specific backend servers, then group servers behind one address. The connect-time authorization check used loose overlap logic instead of containment.

.some() returns true if a single server in the group is on the key's list, so a key scoped to one harmless public server unlocks every server grouped beside it. Bundle a public server and a sensitive internal one into a group, hand out a key for only the public one, and that key reaches the internal server the moment it is pointed at the group. A routine setup choice becomes privilege escalation. 

The same shape appears wherever authorization and action live in different places: a guard on one transport but not the WebSocket beside it, or a check performed once at connect that nothing repeats per call, etc.

CWE-863 Incorrect Authorization

The check runs, but with the wrong predicate and at the wrong time (connect, not call).

Recommendation

Decide permission at the moment of the action, not just at the door. Require full containment (.every()), not overlap, and make sure every route and every transport passes through the same authorization middleware.

2. Read-only that is not really read-only

The first gap is about who gets through the door. This one is about what they can do once inside. Many servers offer a read-only mode and enforce it with a text match, and a text check loses to the database's own parser every time.

We reproduced this against a popular MCP server, where /* c */DROP TABLE customers slipped past the keyword check (since patched upstream; the maintainers now describe that guard as best-effort, not a security boundary). 

The root cause is a parser mismatch: the database tokenizer treats a block comment as whitespace, so the statement the engine executes and the string the check inspected diverge. 

The class recurs across database MCP servers in different shapes, a keyword list that blocks DELETE but not GRANT, a per-tool isWrite:false flag hung on a method that writes, a classifier that files any statement it cannot recognize (an UPSERT, a data-modifying WITH ... DELETE, a stored-procedure call) as "read" and lets it through.

CWE-863 / CWE-184 Incomplete Denylist

A syntactic string match standing in for a semantic guarantee.

Recommendation

Do not decide safety by matching text. Enforce read-only where it is real: connect with a database account that has no write permission, so the guarantee holds for every query the engine will accept, comment tricks included.

3. The confirmation you can skip

Those first two gaps needed a crafted request to open them. This one can trip on its own. Dangerous tools often claim a human is in the loop, then quietly fail open the moment the confirmation channel is unavailable.

We found this across several infrastructure and identity MCP servers: a destructive operation ran with no confirmation because the gate defaulted to "approved" whenever it could not ask. The variants are always a control that fails open, an auto-confirm fallback when the client cannot prompt, a caller-settable confirm flag that the party being controlled gets to supply, or a normalization gap that lets a spelling variant of the operation slip the check entirely.

CWE-636 Not Failing Securely (fail open)

The safe state on error is refusal, and the code chose the opposite.

Recommendation

A confirmation must fail closed. If there is no way to ask a human, refuse the action. Never let the party being controlled supply the "already confirmed" signal.

4. The allow-list that only prints the menu

Those three gaps all live inside the server's own logic. This one breaks a boundary the deployer thought they had drawn. Deployers use allow-lists to limit which tools an agent may call, and too often the filter is applied only when building the list shown to the model, not at execution. The menu is filtered; the kitchen is not.

We found this in several payment and infrastructure toolkits. The model is shown list_orders and get_status; the executor still holds refund_payment and delete_account and will run either one if the name arrives in a tool call. If a hallucinating or prompt-injected model emits a name that was never on the menu, the executor prepares the order anyway. The desync is the whole bug: the security decision was made against one set of tools and the dispatch happens against another.

CWE-863 Incorrect Authorization

Enforcement at list-build time, dispatch against the unfiltered catalog.

Recommendation

The executor is the security boundary, not the menu builder. Re-check the allow-list at dispatch, against exactly the same filtered set the model was given.

5. Taking the caller's word

So far the tool has done more than it advertised. Here it does exactly what it is told, using a value it should never have trusted. Security-critical values, file paths and outbound URLs above all, must come from server-side configuration, never from the untrusted model or a connected server. When they do not, two classic sinks open up.

We found server-side request forgery where a server-supplied fragment of the request URL let the target host be rewritten to an internal or cloud-metadata address, with the response handed straight back to the model. 

The mechanism is a URL-parsing quirk: when an attacker-controlled path segment begins with @host, a naive base + route concatenation makes the trusted base into URL "userinfo" and the parser resolves the attacker's host instead. 

A close cousin lives in schema handling: a tool's input schema, fully controlled by a connected server, is passed to a JSON $ref resolver whose default loader will fetch http:// and read file:// references, turning schema processing at agent startup into blind SSRF and local file read. 

We also found path-traversal writes where a model-supplied name climbed out of the intended directory and dropped attacker-chosen bytes elsewhere on disk.

CWE-918 Server-Side Request Forgery, CWE-22 Path Traversal

Trust placed in a value that crossed the wire.

Recommendation

Treat every value from the model or a connected server as hostile. Contain every path (resolve to an absolute path, then verify it is still inside the root). Allow-list outbound hosts, reject userinfo and host changes, and pin DNS between the check

Key takeaway

Most MCP bugs are not exotic. They are a check that lives one layer away from where it is needed, and a server's advertised restriction is only as real as the thing that enforces it on the path the action actually takes.

If you develop an MCP server

Audit your execution boundaries today. Put every check where the action happens, not just at intake. Enforce read-only with a real database account, make every gate fail closed, re-check the allow-list in the executor, contain paths, allow-list outbound hosts, and treat both the client you talk to and the servers your agent connects to as hostile.

If you deploy an MCP server

Give each tool the minimum credentials it truly needs, assume a server's advertised restrictions are best-effort until you have verified them yourself, and never point an agent at a registry server you would not be willing to run yourself.

Where Metano fits

The discussions in this post point at one thing: MCP servers are just like any other software, they may have vulnerabilities, they may fail to enforce the safety which they claim. That is the gap Metano is built to close. We are building runtime security for the AI Agents ecosystem. Rather than trusting each MCP server's advertised limits, Metano gives security teams control over which MCP servers and tools to allow / deny in their network. Along with that, Metano also provides realtime visibility and threat detection during the invocation of MCP server tools as and when they get invoked by AI Agents.