mcp-server-kubernetes Tool Access Control Bypass (CVE-2026-46519)
mcp-server-kubernetes ships three operator-facing controls for limiting which Kubernetes tools an agent can use: ALLOW_ONLY_READONLY_TOOLS, ALLOW_ONLY_NON_DESTRUCTIVE_TOOLS, and a custom ALLOWED_TOOLS allowlist. CVE-2026-46519 (CVSS 8.8) reveals that all three were applied only when listing tools and were ignored when a tool was actually called. Any authenticated client that already knew a tool name, such as kubectl_delete or exec_in_pod, could invoke it directly regardless of the configured restriction. Where the server held a cluster-admin service account, a documented dev and staging pattern, that gap was equivalent to full cluster compromise. Fixed in version 3.6.0.
What happened
mcp-server-kubernetes lets AI clients such as Claude Code and Cursor drive a Kubernetes cluster through MCP tool calls. To let operators run it safely, the project documents three environment variables that restrict the tool surface: ALLOW_ONLY_READONLY_TOOLS limits agents to read operations, ALLOW_ONLY_NON_DESTRUCTIVE_TOOLS blocks deletes and other destructive actions, and ALLOWED_TOOLS takes an explicit allowlist of tool names.
The flaw is that these checks lived in only one place. The filtering logic ran inside the ListToolsRequestSchema handler, which answers tools/list and decides what an agent can see. It was absent from the CallToolRequestSchema handler, which answers tools/call and decides what an agent can actually run. Restricted tools were hidden from the catalogue but remained fully executable.
An agent or attacker did not need to discover anything. The MCP tool names are public and stable. A client that set ALLOWED_TOOLS=kubectl_get and trusted it could still send a tools/call for kubectl_delete, exec_in_pod, kubectl_generic, or any node management tool, and the server would execute it. The allowlist was real at discovery and cosmetic at execution.
The blast radius is set by the service account the server runs under. The project documents running with cluster-admin as a common convenience in development and staging. In that configuration the bypass is not a partial escalation, it is arbitrary control of the cluster from any client that can reach the server. The maintainer fixed it in version 3.6.0 by applying the same restriction logic inside the tools/call handler, so a hidden tool is now also a blocked tool.
The PolicyLayer angle
This is the clearest possible illustration of why access control has to live in the request path, not in the interface. The operator did everything the documentation asked: configured the allowlist, deployed, trusted it. The control was sound at the layer it was written for and absent at the layer that mattered. Filtering tools/list changes what an agent is shown; it does nothing to what an agent can invoke. Only a check on tools/call, against the actual tool and its actual arguments, decides what runs.
The structural problem is that the server was asked to police itself. Its allowlist ran in the same process as the tools it was meant to restrict, and the two handlers could drift out of agreement without anyone noticing, because the visible behaviour, the tool list, still looked correct. PolicyLayer moves that decision out of the server. Every tools/call crosses the control plane first and is evaluated against policy before it reaches the upstream, so a tool that policy forbids cannot be called even if the agent knows its name, even if the server would have happily run it, and even if the server's own filtering is missing or wrong. The same control covers every connected MCP server, not one allowlist re-implemented per server.
A policy here is argument-aware, not just name-aware. Allowing kubectl_get while denying kubectl_delete is the floor; the same evaluation point can permit reads scoped to a namespace, require a manual approval gate for anything destructive, and reject a kubectl_generic call whose arguments amount to a delete. Enforcement at the execution boundary, per call, against real arguments, is exactly the control that was missing here.
Mitigations
Upgrade mcp-server-kubernetes to version 3.6.0 or later, which applies the restriction logic at tools/call. Until you have upgraded, treat all three environment variables as non-functional for enforcement: assume any tool the server implements can be called, and do not rely on ALLOWED_TOOLS or the readonly and non-destructive flags as a security boundary. Stop running the server with a cluster-admin service account; bind it to a least-privilege role scoped to the operations the agent genuinely needs, so a bypass cannot exceed that role. Restrict network reachability to the server's endpoint, and evaluate every tool call against policy at a control point the server cannot bypass, rather than trusting the server's own filtering.
FAQs
An access control bypass in mcp-server-kubernetes (versions before 3.6.0, CVSS 8.8). The server's three tool-restriction controls, ALLOW_ONLY_READONLY_TOOLS, ALLOW_ONLY_NON_DESTRUCTIVE_TOOLS, and ALLOWED_TOOLS, were enforced when listing tools but not when calling them. Any client that knew a restricted tool's name could invoke it directly.
No. On versions before 3.6.0 the allowlist only filters what tools/list returns. The tools/call handler does not consult it, so a client can call a tool that is not on the allowlist as long as it knows the name. MCP tool names are public, so this offers no protection at execution. Upgrade to 3.6.0, where the check is applied at execution.
It depends on the privileges of the service account the server runs with. mcp-server-kubernetes documents running with cluster-admin as a common development and staging setup. With that account, an attacker who can reach the server can run any tool, including destructive ones such as kubectl_delete and exec_in_pod, which is equivalent to full control of the cluster.
PolicyLayer evaluates every tools/call against policy in the request path before it reaches the MCP server. A denied tool cannot be called even if the agent knows its name and even if the server's own filtering is missing, because the decision does not depend on the server enforcing it. Policies are argument-aware, so destructive operations can be blocked or gated behind manual approval rather than only hidden from a list.