




2938 Members
The recent MCP incidents are a useful wake-up call: passing authentication at the edge does not mean every downstream tool invocation is safe. The gap is subtle, and attackers are now very good at finding it.
What makes this moment important is not just any single bug, but the repeated pattern. We keep seeing systems that verify identity once, then implicitly trust later operations that should have had their own runtime checks. That is exactly where modern MCP authorization needs to mature.

In plain terms, the fast-mcp-telegram CVE was a token handling bug. The project used Bearer tokens to select a session file path, but weak Bearer token validation let crafted input abuse path traversal and reach the protected default Telegram session anyway. So a caller could bypass intended protections and act as that session user. See the incident write-up and advisory in Cyber Security News and the GitHub advisory GHSA-rxw2-pc8j-vxwm.
The LiteLLM MCP CVE story is different but equally instructive. LiteLLM had MCP test routes that, under certain conditions, could be exploited to spawn subprocesses; initially this was considered “authenticated only,” but researchers showed it could be chained with a Starlette host-header bypass to become unauthenticated RCE in affected deployments. The chain is documented by Horizon3.ai and the LiteLLM advisory GHSA-v4p8-mg3p-g94g.
Different root causes, same lesson: an MCP auth bypass at one layer can potentially lead to tool misuse if runtime permissions are not re-evaluated per action.
You need this phrase operationalized, not just understood: MCP authentication vs authorization.
Authentication answers: “Who is this caller?”
Authorization answers: “Can this caller perform this exact action on this exact resource under this exact context right now?”
That means transport/session auth can succeed while tool-call authorization should still deny a dangerous call_tool. If those checks are fused, you get brittle call_tool security.
| Control question | Wrong assumption | Correct control |
|---|---|---|
| “Caller has token, so we’re good” | Token = blanket trust | Token proves identity, not permission |
tools/list allowed |
call_tool should also be allowed |
Discovery can be broader than execution |
| Test/admin route authenticated | It’s safe for all authenticated users | Test/admin routes require stricter role + network controls |
| Past approval exists | Reuse forever | Recompute decision at runtime with context |

A lot of teams run all MCP routes at one trust level. That’s the design bug.
tools/list: low-risk discovery, often metadata-only.call_tool: medium-to-high risk execution surface, needs per-tool and per-resource checks.If your policies don’t model this separation explicitly, one credential class can drift into unintended powers. In practice, high-risk classes should require tighter scopes, additional claims, network location constraints, and stronger approval semantics than ordinary inference-time calls.
A usable fail-closed authorization model can be simple and strict:
The key is deterministic denial on uncertainty. Timeouts, PDP errors, missing attributes, unknown tool IDs, malformed inputs, and stale session context should all resolve to deny. That is real fail-closed authorization, ensuring decisions are made with certainty rather than relying on best-effort assumptions.
This is where an MCP Gateway becomes practical: it centralizes policy enforcement right before tool execution, so every call_tool gets a runtime decision and not just inherited transport trust. A good gateway should treat authn as one input signal and still perform independent runtime authorization checks with policy + context + resource sensitivity.
With Permit MCP Gateway, the design target is explicit: enforce runtime decisions across tool classes, keep high-risk admin/test routes on separate policy rails, and produce decision traces that security teams can review quickly. The important architectural point is not branding; it’s enforcing policy at the execution boundary where attacks actually materialize.
tools/list, call_tool, and MCP test endpoints.If you want useful agent audit logs, log decisions as first-class security events, not debug strings. For every request (allow or deny), capture: decision ID, timestamp, subject identity, delegated-on-behalf-of identity, tenant, endpoint type (tools/list, call_tool, admin/test), tool name, action class (read/write/destructive/admin), target resource, policy ID/version, input attributes used, decision result, deny reason code (if denied), auth method, source IP/network zone, request correlation ID, and latency.
Also log policy-evaluation integrity signals: missing attributes, stale cache indicators, fallback behavior, and PDP error states. Without those, incident response cannot distinguish “correct deny” from “system degraded.” This schema is what turns authorization from theory into enforceable operations.
Strong authentication tells you who is at the door, but not what they should be allowed to do once inside. MCP surfaces include many actions with very different risk levels, and those decisions must be made at runtime. If you skip runtime checks, one bypass can expose many tools. That is the core reason MCP authorization must be explicit per call.
A transport auth failure means the caller never proved identity to the API boundary. A runtime authorization failure means identity may be valid, but the requested operation is not allowed under current policy and context. Both are important, but they solve different problems. Treating them as one control is how blind spots appear.
Test endpoints often accept richer configuration and can touch execution internals, so they behave closer to admin functions than user functions. Even when intended for diagnostics, they can become command or connectivity pivots if broadly reachable. That is why MCP test endpoints should have stricter controls than standard call_tool paths. They are high-impact by design.
Done right, it improves security without breaking normal traffic because expected context is modeled and cached appropriately. The system denies only when critical signals are missing, invalid, or risky, which is exactly when permissive behavior is most dangerous. Operationally, teams pair this with clear deny codes and fast debugging paths. Reliability comes from predictable policy behavior, not permissive defaults.
A useful deny record includes who requested what, against which resource, under which policy version, and exactly why denial occurred. It should also include request correlation IDs, network source, and evaluation context health flags. That gives responders both forensic value and remediation direction in one record. Thin logs force teams to guess under pressure.
No, it should complement them. The gateway enforces at tool execution boundaries, while app/data layers enforce where business logic and data sensitivity are ultimately resolved. Defense-in-depth means policy coherence across these layers, not choosing only one. The gateway closes the runtime action gap that perimeter auth leaves open.

AI Engineer, Agent Whisperer, Tesseract Architect, All Things Cyber