MCP server Kubernetes security
MCP server Kubernetes security — RBAC, NetworkPolicy, Secrets CSI, and Pod Security Standards
Running an MCP server on Kubernetes introduces a layer of infrastructure security that Docker containers alone don't address. Kubernetes has its own identity system (service accounts), network model (NetworkPolicy), secrets management layer (Secrets Store CSI Driver), and workload admission controls (Pod Security Standards). A compromised MCP server pod in a misconfigured cluster can escalate to cluster-admin via the default service account token, exfiltrate secrets from other namespaces, or pivot to cloud provider APIs via IMDS. Each risk has a specific Kubernetes control that eliminates it.
Pattern 1: Service account RBAC least-privilege
Kubernetes auto-mounts a service account token in every pod by default. The default service account in many clusters has been granted broad roles — sometimes cluster-admin — by operators who wanted to "just make it work." An attacker who gains code execution in an MCP server pod can read the mounted token at /var/run/secrets/kubernetes.io/serviceaccount/token and use it to query the Kubernetes API for secrets, create new pods, or modify deployments.
# WRONG: MCP server pod using default service account (which may have broad permissions)
apiVersion: apps/v1
kind: Deployment
metadata:
name: mcp-server
spec:
template:
spec:
# No serviceAccountName = uses 'default' service account
containers:
- name: mcp-server
image: myorg/mcp-server:1.2.3
---
# RIGHT: dedicated service account with no roles, auto-mount disabled
apiVersion: v1
kind: ServiceAccount
metadata:
name: mcp-server
namespace: mcp-prod
automountServiceAccountToken: false # disable auto-mount; re-enable only if K8s API calls needed
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mcp-server
namespace: mcp-prod
spec:
template:
spec:
serviceAccountName: mcp-server # dedicated SA
automountServiceAccountToken: false
containers:
- name: mcp-server
image: myorg/mcp-server@sha256:abc123 # digest-pinned image
If the MCP server does need to make Kubernetes API calls (e.g. to read a ConfigMap for dynamic configuration), bind only the minimum required verbs to a namespaced Role — never a ClusterRole — and never grant secrets/get or wildcard resource access:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: mcp-server
namespace: mcp-prod
rules:
- apiGroups: [""]
resources: ["configmaps"]
resourceNames: ["mcp-server-config"] # specific resource only
verbs: ["get", "watch"] # no create/update/delete/patch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: mcp-server
namespace: mcp-prod
subjects:
- kind: ServiceAccount
name: mcp-server
namespace: mcp-prod
roleRef:
kind: Role
name: mcp-server
apiGroup: rbac.authorization.k8s.io
Pattern 2: NetworkPolicy default-deny isolation
Without a NetworkPolicy, all pods in a Kubernetes cluster can communicate with each other freely. A compromised MCP server can probe databases, internal APIs, and other MCP server pods in any namespace. NetworkPolicy lets you define what traffic the pod is allowed to initiate or receive at the network layer, independent of application-level controls.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: mcp-server-deny-all-then-allow
namespace: mcp-prod
spec:
podSelector:
matchLabels:
app: mcp-server
policyTypes: [Ingress, Egress]
ingress:
# Allow only from the ingress controller
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
ports:
- protocol: TCP
port: 3000
egress:
# Allow DNS resolution
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53
# Allow only to specific downstream services by label
- to:
- podSelector:
matchLabels:
app: postgres
ports:
- protocol: TCP
port: 5432
# Block: IMDS (169.254.169.254), other pods, external IPs not listed above
# NetworkPolicy is additive allowlisting — anything not listed is denied
Pattern 3: Secret Store CSI Driver instead of Kubernetes Secrets
Kubernetes Secrets are only base64-encoded, not encrypted at rest by default. Any pod with secrets/get permission in a namespace can read all secrets in that namespace. The Secrets Store CSI Driver integrates with external secret stores (AWS Secrets Manager, HashiCorp Vault, Azure Key Vault, GCP Secret Manager) and mounts secrets directly as files in the pod's filesystem — they never touch the Kubernetes etcd database.
# WRONG: storing credentials as a Kubernetes Secret (base64-encoded in etcd)
apiVersion: v1
kind: Secret
metadata:
name: mcp-server-db-credentials
namespace: mcp-prod
type: Opaque
data:
DB_PASSWORD: c2VjcmV0cGFzc3dvcmQ= # base64("secretpassword") — readable by anyone with secrets/get
---
# RIGHT: Secret Store CSI with AWS Secrets Manager
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: mcp-server-secrets
namespace: mcp-prod
spec:
provider: aws
parameters:
objects: |
- objectName: "prod/mcp-server/db-credentials"
objectType: "secretsmanager"
jmesPath:
- path: "password"
objectAlias: "db-password"
---
# Pod spec that mounts secrets via CSI
spec:
volumes:
- name: secrets-store
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: mcp-server-secrets
containers:
- name: mcp-server
volumeMounts:
- name: secrets-store
mountPath: /mnt/secrets
readOnly: true
env:
# Application reads from file, not env var — avoids /proc/PID/environ exposure
- name: DB_PASSWORD_FILE
value: /mnt/secrets/db-password
Pattern 4: Pod Security Standards restricted profile
The Kubernetes Pod Security Standards define three profiles: privileged, baseline, and restricted. The restricted profile enforces that pods run as non-root, drop all Linux capabilities, use a read-only root filesystem, disallow privilege escalation, and use a seccomp runtime default profile. Enforcing the restricted profile via namespace labels means that any Deployment that violates these constraints is rejected at admission — even if someone accidentally adds privileged: true to the container spec.
# Label the namespace to enforce the restricted profile
kubectl label namespace mcp-prod \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/warn=restricted \
pod-security.kubernetes.io/audit=restricted
---
# Pod spec that passes the restricted profile
spec:
securityContext:
runAsNonRoot: true
runAsUser: 65534
runAsGroup: 65534
fsGroup: 65534
seccompProfile:
type: RuntimeDefault
containers:
- name: mcp-server
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
volumeMounts:
- name: tmp
mountPath: /tmp # writable tmpfs for any temp file needs
volumes:
- name: tmp
emptyDir: {} # in-memory only, not persisted to node disk
These four Kubernetes controls — service account RBAC, NetworkPolicy isolation, Secrets Store CSI, and Pod Security Standards — form the baseline for any production MCP server deployment. SkillAudit's infrastructure scan checks for these patterns in submitted Kubernetes manifests alongside the application-level security analysis. For a full review, run a SkillAudit scan. Related: container security, secrets management, network security.