MCP started as a way to wire one user’s AI client to a single backend. That’s not how it’s deployed anymore. A single gateway now fronts a dozen internal servers, or serves dozens of customer organizations off the same infrastructure.
Tool-level governance won’t catch this. Scope the tool tightly, validate the schema, do everything by the book, it still assumes one caller. Start serving multiple tenants off that same server, and none of that tells you whether they’re actually walled off from each other.
Most teams haven’t tested for it, because the failures don’t show up in functional testing. Verifying that search_tickets returns the correct ticket doesn’t test whether two tenants racing for the same cache slot see each other’s data.
Where it Actually Breaks
The wrong key, almost every time I’ve seen this bug, is connection ID instead of tenant. It’s an easy mistake to miss because at launch it’s true: one connection really does belong to one tenant. Months pass before anything changes. Then a pooling change goes in, done purely for latency, nobody even thinking about the cache, and the assumption the whole key depended on just isn’t there anymore. Two tenants can now share a connection. Whoever’s request is cached last is the one the other gets served.
Two disclosed CVEs fall into this category, and it’s worth noting that they’re not the same bug under two names. CVE-2026-25536 hit the TypeScript SDK, versions 1.10.0 through 1.25.3. Reusing a transport or server instance under concurrent load could cause a response to be sent to a client that never asked for it, which becomes a tenant problem the moment distinct clients happen to be distinct tenants, which is most of the time. CVE-2026-52869 is further down the stack. In version 1.27.1, the Python SDK’s HTTP transports would take a session ID at face value and never verify that the tenant presenting it was the one who created it. They’re unrelated failures that happen to fall into the same category, not two versions of the same mistake.
**Proxied credentials. **Plenty of MCP servers hold a credential on someone else’s behalf, maybe a database connection, maybe a delegated OAuth grant, and use it whenever a caller needs it. Nothing wrong with that by itself. The problem shows up the moment tenant resolution can quietly fail. n8n-MCP had exactly this bug in its multi-tenant mode, before version 2.51.2: fail to resolve the tenant, and the server would just use the operator’s own instance credentials rather than turning the request away. Someone found it. It shipped before they did.
Tool metadata crossing tenant boundaries. Say a gateway lets tenants register their own custom tools. Pay attention to how it caches the tools/list response, because if that cache is keyed by connection instead of tenant, tenant A’s tool definitions can end up sitting inside tenant B’s context. Whether that leaked content turns out to be malicious is a separate question. The leak itself is the bug, and it’s worth going after regardless of what’s in it.
Reasons for Current Gaps
Distributed systems have leaked shared state this way for decades. What’s actually new is narrower than people think: MCP gives you a standard way to put metadata directly into a model’s context. That one addition is enough to give an old bug a new way to spread.
So why hasn’t anyone tested for it? One of the primary reasons is that teams started this as an experiment for a few customers. Prompt injection and tool permissions dominate the security conversation right now, and that’s a genuinely different problem from cross-tenant leakage, but it eats the attention that should go to this. And audits only cover what’s in scope for that engagement. If host-side tool registries never made it into the system description, nobody’s testing them, not because anyone decided to skip it, just because nobody wrote it down.
Start on Paper, Not in Code
Before you write a single test, trace one request through the system by hand. Where does its tenant identity actually go, through the session, the connection, all the way down to whatever credential a downstream call ends up using? Most teams have never written this down. It’s usually enough on its own to catch the first bug, well before any test suite exists to catch it for you.
Here are two initial tests that you can conduct.
Test 1: Pull up tenant B’s tool list while the cache is still warm from tenant A’s last request. If tenant A’s tools show up in it, you’ve found your first real bug, and you found it without a load generator, just a warm cache and a second tenant account.
Test 2: Hand tenant C an expired session ID and see where the credential comes from. In most implementations that haven’t thought about this specifically, nothing stops the request. It just gets serviced using whatever credential the server last resolved successfully, which belongs to a different tenant entirely.
Load Testing: CVE-2026-25536 checks for a scenario in which a response is delivered to a tenant who didn’t request it when connections or server instances are shared under load. You need hooks in the code that deliberately force two requests to collide at the exact instant a cache gets written. Plan on hundreds of iterations before you trust a clean result.
Conclusion
All requests should have a tenant identity that is not derived from a connection object or client ID. You can pass the right tenant ID all the way down and have a query ignore it, or a cache key leave it out. Credential lookups need to be fail-closed, and cache keys need to include a tenant. Cross-tenant isolation needs a separate budget and not be bundled with functional tests and single-tenant security work most teams already run. For most MCP deployments today, nobody has written that test.