Kubernetes Security · RBAC · Service Accounts

MCP server Kubernetes RBAC security — service account least-privilege audit

Every MCP server Pod in Kubernetes runs as a service account, and the RBAC bindings attached to that account determine what the compromised process can do inside the cluster. Wildcard resource grants, secrets/get permissions, and ClusterRoles scoped cluster-wide instead of to the MCP namespace are the three most common misconfigurations that turn an MCP server compromise into a cluster-wide privilege escalation. This reference covers how to enumerate exactly what your MCP server's service account can do, what to look for, and how to reduce it to the minimum necessary surface.

Enumerating MCP server SA permissions with kubectl

Before you can reduce permissions you need to know what the service account currently has. The kubectl auth can-i --list subcommand accepts an --as flag that impersonates any service account, printing every resource/verb combination the SA can perform. Run it against your MCP server's SA in its deployment namespace:

# Full permission dump for the mcp-server SA in the mcp-prod namespace
kubectl auth can-i --list \
  --as=system:serviceaccount:mcp-prod:mcp-server \
  --namespace mcp-prod

# Example output (dangerous configuration):
# Resources                                   Verbs
# *.*                                         [*]
# secrets                                     [get list watch create update delete]
# pods/exec                                   [create]
# pods/attach                                 [create]
# deployments.apps                            [get list watch update patch]

# Safe output looks like:
# configmaps                                  [get watch]
# (nothing else)

For a more readable cross-resource view, use rakkess (access matrix tool). It shows a compact table of what the SA can do across every resource type registered in the cluster, making it easy to spot unexpected grants at a glance:

# Install rakkess via krew plugin manager
kubectl krew install access-matrix

# Show what mcp-server SA can do across all resource types
kubectl access-matrix \
  --as=system:serviceaccount:mcp-prod:mcp-server \
  --namespace mcp-prod

# Output columns: Resource | get | list | create | update | delete | watch
# Look for any row with a checkmark in create/update/delete for sensitive resources

To answer the inverse question — who can perform a specific sensitive action — use kubectl-who-can:

# Who can read secrets in the mcp-prod namespace?
kubectl who-can get secrets --namespace mcp-prod

# Who can exec into pods cluster-wide?
kubectl who-can create pods/exec

# Who can list all secrets cluster-wide?
kubectl who-can list secrets

Dangerous RBAC misconfigs: wildcards, secrets, and ClusterRole scope

Three patterns are reliably dangerous for MCP server service accounts. The first is wildcard resource access — resources: ["*"] — in any role bound to the SA. This grants the SA the ability to read, modify, or delete every API resource in scope, including secrets, pods, and deployments. A single prompt injection or SSRF that reaches the Kubernetes API via the automounted service account token turns into full namespace or cluster ownership:

# DANGEROUS — wildcard ClusterRole bound to MCP server SA
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: mcp-server-role  # ClusterRole: applies cluster-wide
rules:
- apiGroups: ["*"]
  resources: ["*"]       # Every resource type
  verbs: ["*"]           # Every verb including create/delete/escalate

---
# This ClusterRoleBinding makes it cluster-wide (not namespace-scoped)
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: mcp-server-binding
subjects:
- kind: ServiceAccount
  name: mcp-server
  namespace: mcp-prod
roleRef:
  kind: ClusterRole
  name: mcp-server-role
  apiGroup: rbac.authorization.k8s.io

The second dangerous pattern is secrets/get permission. Kubernetes Secrets store TLS certificates, database passwords, API keys, and other MCP server credentials. An SA that can GET secrets can read every secret in scope — including the secrets of every other workload in the namespace. The third is using ClusterRole + ClusterRoleBinding instead of a namespace-scoped Role + RoleBinding. Even a narrow ClusterRole (only configmaps/get) scoped cluster-wide lets the MCP server read ConfigMaps in every namespace including kube-system.

Minimum-permission RBAC for a typical MCP server

A typical MCP server needs to read its own ConfigMap for configuration and possibly watch it for live reload. It does not need to read secrets (credentials should be injected via environment variables from a secret volume, not fetched via the API), exec into pods, list other workloads, or access any other namespace. The minimum-permission RBAC manifest looks like this:

# SAFE — namespace-scoped Role with exactly the permissions needed
apiVersion: v1
kind: ServiceAccount
metadata:
  name: mcp-server
  namespace: mcp-prod
automountServiceAccountToken: false  # Opt out: no API access needed at all
                                      # Only set true if the SA must call the K8s API

---
# If the SA does need API access, create a minimal namespace-scoped Role
apiVersion: rbac.authorization.k8s.io/v1
kind: Role                            # Role: namespace-scoped, not ClusterRole
metadata:
  name: mcp-server-role
  namespace: mcp-prod
rules:
- apiGroups: [""]
  resources: ["configmaps"]
  resourceNames: ["mcp-server-config"]  # Only THIS specific ConfigMap by name
  verbs: ["get", "watch"]               # Only get and watch; no list, create, update, delete

# Explicitly NOT granted:
# - secrets (any verb)
# - pods/exec or pods/attach
# - deployments, services, or any other resource type
# - any verb in other namespaces

---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding                     # RoleBinding: namespace-scoped
metadata:
  name: mcp-server-binding
  namespace: mcp-prod
subjects:
- kind: ServiceAccount
  name: mcp-server
  namespace: mcp-prod
roleRef:
  kind: Role                          # Binds to Role, not ClusterRole
  name: mcp-server-role
  apiGroup: rbac.authorization.k8s.io

Notice automountServiceAccountToken: false on the ServiceAccount itself. This prevents Kubernetes from mounting the SA token into every Pod that uses this account, even if the Pod's spec does not explicitly request it. Set this to false as the default for all MCP server SAs, and override it to true only on Pods that genuinely need to make Kubernetes API calls.

Using rbac-lookup to audit binding chains

RBAC bindings can be indirect: a service account may be in a group, and the group may be bound to a ClusterRole. Standard kubectl auth can-i resolves these chains, but rbac-lookup makes the binding chain human-readable:

# Install rbac-lookup via krew
kubectl krew install rbac-lookup

# Show all roles/clusterroles bound to the mcp-server SA
kubectl rbac-lookup mcp-server --kind ServiceAccount --namespace mcp-prod

# Example output:
# SUBJECT           SCOPE        ROLE
# mcp-server (SA)   mcp-prod     Role/mcp-server-role
# mcp-server (SA)   cluster-wide ClusterRole/view    ← unexpected cluster-wide binding

# Find all SAs bound to ClusterRoles (cluster-wide scope = high risk)
kubectl rbac-lookup --output wide | grep -E "cluster-wide.*ClusterRole" | grep mcp

The view ClusterRole is a built-in role that grants read access to most resource types across the entire cluster. It is frequently attached to SAs as a "safe read-only role" — but it includes secrets/get in many Kubernetes versions and grants visibility into every namespace's workloads. Never bind the built-in view ClusterRole to an MCP server SA.

SkillAudit findings

CRITICAL Wildcard resources in ClusterRole bound to MCP server SA. resources: ["*"] or verbs: ["*"] in a ClusterRole grants the SA ability to read, modify, or delete every API object in the cluster. A single SSRF to the Kubernetes API service yields full cluster control. Remove the wildcard and enumerate only required resources explicitly.
CRITICAL secrets/get permission on MCP server SA. The SA can read Kubernetes Secrets in its namespace (or cluster-wide if via ClusterRole). This includes the TLS keys, database passwords, and API tokens of all other workloads in scope. Revoke this permission; inject credentials via projected secret volumes at Pod startup instead of fetching them via the API.
HIGH ClusterRole instead of namespace-scoped Role. The MCP server SA is bound via ClusterRoleBinding rather than a namespace-scoped RoleBinding. Even if the ClusterRole's permissions appear narrow, they apply across all namespaces. Replace with a namespaced Role + RoleBinding unless cluster-wide access is explicitly required and documented.
HIGH automountServiceAccountToken not set to false. The ServiceAccount or Pod spec does not set automountServiceAccountToken: false. The SA token is mounted into the container filesystem at /var/run/secrets/kubernetes.io/serviceaccount/token and is accessible to any process in the container. If the MCP server does not call the Kubernetes API, disable token mounting.
MEDIUM pods/exec permission on MCP server SA. The SA can open an exec session into any Pod in scope. This is rarely required for an MCP server and provides interactive shell access to other workloads in the namespace. Remove the pods/exec subresource grant from the role.

Paste a GitHub URL at skillaudit.dev to get a graded report card covering RBAC configuration alongside all other MCP security dimensions.