{"slug": "four-ways-mcp-authorization-breaks-in-production", "title": "Four ways MCP authorization breaks in production", "summary": "LiteLLM patched CVE-2026-59822, an authentication bypass in its MCP endpoint rated 8.8 High on CVSS 4.0 that affected every version below 1.84.0 and was added by CISA to the Known Exploited Vulnerabilities catalog on 2 September 2026 with a 16 September remediation deadline for federal civilian agencies. The flaw substituted an empty UserAPIKeyAuth() object when the gateway's key validation failed, letting any fabricated Bearer token open an authenticated MCP session, and a second bypass in the same pull request let a \".well-known\" substring anywhere in a request URL mark protected routes public. The bugs violate the 2026-07-28 MCP authorization spec, which requires servers to validate that tokens were issued for their use and forbids passing through client tokens.", "body_md": "# Four ways MCP authorization breaks in production\n\nThe 2026-07-28 spec tells you exactly what to do. These are the code shapes that ignore it.\n\nIn April 2026, a pull request landed in LiteLLM that closed an authentication bypass in its MCP endpoint. When the gateway's own key validation failed, an OAuth2 passthrough fallback substituted an empty `UserAPIKeyAuth()` object instead of rejecting the request. Any fabricated Bearer token produced an authenticated MCP session. No valid key required. An attacker who could reach the route could list and call tools as though they had passed the gate.\n\nThat is CVE-2026-59822. It affects every version below 1.84.0, the advisory rates it 8.8 High on CVSS 4.0, and CISA added it to the Known Exploited Vulnerabilities catalog on 2 September 2026 with a 16 September remediation deadline for federal civilian agencies. KEV listings require evidence of active exploitation, not a high score alone.\n\nThe CVE itself is not the interesting part. The interesting part is that the bug is one branch of control flow, in a component nobody had classified as an identity boundary, violating a requirement the MCP specification states in plain language.\n\nThe 2026-07-28 authorization spec is unusually prescriptive. Servers MUST validate that tokens presented to them were issued for their use. Servers MUST NOT pass through the token received from the client. Clients MUST send the RFC 8707 `resource` parameter. Clients MUST verify PKCE support before proceeding and MUST refuse to continue if the authorization server metadata omits `code_challenge_methods_supported`. Very little is left to interpretation.\n\nSo when MCP authorization fails in production, it is usually not because the spec was ambiguous. It is because a small number of code shapes keep recurring. Here are four, with the spec clause each one walks past.\n\n## 1. Auth that degrades open\n\nThe LiteLLM bug is the cleanest example of the first shape: a failed authentication check that assigns a default identity instead of raising.\n\nIn outline, rather than in LiteLLM's actual source:\n\nAn empty auth object is not the same thing as no auth object. Downstream code reads it as a caller who simply has no limits attached: no budget, no allowed-models list, no team scoping. The request proceeds with the permissions of nobody in particular, which in practice means the permissions of the process handling it.\n\nThis shape shows up wherever passthrough and local auth share a code path. A gateway has to decide whether a credential is *for* it or merely passing *through* it, and when it cannot tell, the safe answer is 401. The real fix gates the fallback correctly: a helper named `_target_servers_use_oauth2` now permits the empty-auth path only when every resolved target server carries an operator-configured `auth_type` of `oauth2`, and routes matching no known server pattern fail closed.\n\nThe rule generalizes. A passthrough path is entered because an operator configured it, never because another check failed.\n\n**What the spec says:** MCP servers MUST validate access tokens before processing the request, and MUST take all necessary steps to ensure no data is returned to unauthorized parties.\n\n## 2. Public-route checks that match the wrong thing\n\nThe same pull request closed a second bypass, which the advisory never mentions.\n\nMCP servers need some routes to be reachable without a token. Protected resource metadata under `/.well-known/` has to be fetchable by a client that does not yet have credentials, which is the whole point of discovery. So implementations grow a function that answers \"is this route public?\"\n\nLiteLLM's version asked whether the string `\".well-known\"` appeared anywhere in the request URL. Appending `?.well-known` to any MCP route therefore marked it public. The fix narrows the test to the path component:\n\nTwo things worth taking from this. First, substring matching against a full URL is not a routing decision, because the query string is attacker-controlled and is part of that string. Second, and more useful for triage: you were exposed to this one whether or not you had ever enabled OAuth2 passthrough. \"We don't use passthrough\" was not a reason to stay on an old version.\n\nIf your server has a public-route allowlist, check what it matches against. Path, exact prefix, after normalization. Not the URL.\n\n## 3. Token passthrough\n\nThe first two shapes are mistakes. This one is a decision, which makes it harder to dislodge.\n\nToken passthrough is when an MCP server accepts a token from a client, does not verify that the token was issued for itself, and forwards it unchanged to a downstream API. It is convenient. It means you do not have to stand up an authorization server, and the downstream API already knows how to validate the token.\n\nIt is also the only MUST NOT in the authorization spec's security considerations:\n\n*\"If the MCP server makes requests to upstream APIs, it may act as an OAuth client to them. The access token used at the upstream API is a separate token, issued by the upstream authorization server. The MCP server MUST NOT pass through the token it received from the MCP client.\"*\n\nThe failure mode is the confused deputy. Your MCP server holds credentials and network reach that its callers do not. If it will forward whatever token arrives, a token minted for server A can be replayed against server B, and your server becomes the instrument for access the caller was never granted. The spec addresses the proxy variant directly: MCP proxy servers using static client IDs MUST obtain user consent for each dynamically registered client before forwarding to third-party authorization servers.\n\nTwo mechanisms stop it, and they work as a pair:\n\n- **Audience binding.** Servers MUST only accept tokens that name them in the audience claim, and MUST reject everything else. This is a validation step on the inbound side, and it is cheap.\n- **RFC 8707 resource indicators.** Clients MUST send the`resource` parameter so the authorization server knows which resource server the token is for. This is what makes audience binding possible in the first place.\n\nWhen your server needs to call upstream, the correct pattern is RFC 8693 token exchange: trade the inbound token for a new, audience-bound token scoped to that specific upstream. We walk through the exchange mechanics in more detail in [OAuth's On-Behalf-Of flow for AI agents](https://workos.com/blog/oauth-on-behalf-of-ai-agents).\n\n## 4. A static key is not an identity\n\nThe last shape is the most common and the least likely to produce a CVE, because nothing is technically broken.\n\nAstrix Research scanned 5,205 open-source MCP servers in October 2025 and found that 53% rely on static API keys or personal access tokens that never expire, while only 8.5% use OAuth. A separate BlueRock scan of roughly 7,000 public servers found 41% required no authentication at all. The two studies disagree on specifics, because scanning open-source repositories and probing deployed remote servers measure different populations, and scanner false-positive rates in this space are high enough that any single percentage deserves suspicion. The direction is not in dispute.\n\nA shared static key gives you no per-caller identity, no audience restriction, and no revocation that is not a full outage. You cannot revoke it for one bad actor without breaking every caller at once. You cannot tell from a log line which client made a call. You cannot scope one caller more tightly than another.\n\nThis persists because of how MCP servers get built. A team treats the server as plumbing, gives it infrastructure-grade auth, a key in an environment variable and maybe an IP allowlist, and ships. Then the same process starts brokering tool calls into ticketing systems, source control, and databases, and the blast radius widens without anyone revisiting the auth model.\n\nA useful test: what does an attacker get from one successful request to this component? If the answer includes \"a tool call against a system of record,\" it needs an authorization server in front of it, with per-client identity, audience-restricted tokens, and revocation.\n\nThe stateless core introduced in 2026-07-28 makes this more pressing, not less. Removing the initialize handshake and the protocol-level session pushes servers toward per-request authorization, and per-request authorization only works if something is actually issuing and validating those tokens.\n\n## How to tell whether you were exposed\n\nFor the LiteLLM CVE specifically, version is the signal, not the disclosure date. The patch shipped on 14 May 2026, seven weeks before the advisory and sixteen before the CISA listing. If you were below 1.84.0 during that window you were exposed regardless of when you heard about it. Upgrade to 1.84.0 or later, or deny `/mcp/` at the edge until you can, then rotate every provider key and downstream credential the gateway could read.\n\nFor your own servers, five questions:\n\n1. Does any authentication path assign a default or empty identity when a check fails, rather than returning 401?\n2. Does any public-route check run against the full URL rather than the normalized path?\n3. Does the server validate the `aud` claim, and reject tokens that do not name it, before processing the request?\n4. When the server calls upstream, does it forward the inbound token, or exchange it for an audience-bound one?\n5. Can you revoke access for one caller without breaking the others?\n\nThe first two are grep-able in an afternoon. The last three are architectural, and the answers tend to arrive together.\n\n## The gap is not the spec\n\nMCP authorization has a well-specified correct answer. The requirements are written as MUSTs, the underlying RFCs are mature, and the 2026-07-28 revision tightened the parts that were previously vague. What the ecosystem does not have is a large population of teams who have stood up an authorization server before. MCP servers are being built by application developers, at speed, in a protocol where the fastest path to working code is a static key and a permissive default.\n\nThat is the gap these four shapes live in.\n\nIf you are building MCP servers and want per-client identity, audience-bound tokens, and revocation without writing an authorization server, that is what AuthKit does. If you are working on the enterprise side of this, where an admin authorizes the server once in the company IdP and every employee's client connects without a consent screen, start with [Inside the ID-JAG](https://workos.com/blog/mcp-enterprise-managed-authorization-id-jag), which covers the validation steps Enterprise-Managed Authorization adds on top of everything here.\n\n## Sources\n\n- [CVE-2026-59822: LiteLLM's MCP auth bypass, and the second bug in the same fix](https://skycloak.io/blog/cve-2026-59822-litellm-mcp-auth-bypass-ai-gateway-identity/) , Skycloak, 21 September 2026\n- [Authorization security considerations](https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization/security-considerations) , MCP specification 2026-07-28\n- [MCP security statistics 2026](https://www.practical-devsecops.com/mcp-security-statistics-2026-report/) , Practical DevSecOps (aggregating Astrix Research and BlueRock scans)\n- GitHub Security Advisory GHSA-7488-6r32-c95q, reported by `yaaras`", "url": "https://wpnews.pro/news/four-ways-mcp-authorization-breaks-in-production", "canonical_source": "https://workos.com/blog/mcp-authorization-failures", "published_at": "2026-09-23 00:00:00+00:00", "updated_at": "2026-09-23 13:30:47.340108+00:00", "lang": "en", "topics": ["ai-agents", "agent-protocols", "ai-safety", "ai-policy"], "entities": ["LiteLLM", "CVE-2026-59822", "CISA", "Known Exploited Vulnerabilities catalog", "Model Context Protocol", "UserAPIKeyAuth", "CVSS 4.0", "RFC 8707"], "alternates": {"html": "https://wpnews.pro/news/four-ways-mcp-authorization-breaks-in-production", "markdown": "https://wpnews.pro/news/four-ways-mcp-authorization-breaks-in-production.md", "text": "https://wpnews.pro/news/four-ways-mcp-authorization-breaks-in-production.txt", "jsonld": "https://wpnews.pro/news/four-ways-mcp-authorization-breaks-in-production.jsonld"}}