Securing AI agent MCP traffic on Kubernetes requires two enforcement layers: Kyverno admission policies control what gets deployed, and agentgateway External Authorization controls what those deployments can do at runtime. This post covers complete Kyverno 1.17 ValidatingPolicy YAML for agent pods, gateway ExtAuthz configuration, namespace isolation automation, and Shadow MCP detection.
Your cluster has an agent pod running. That agent discovered an MCP server offering a kubectl_exec tool. Nothing stopped it from calling that tool against any namespace it could reach. The agent did not need elevated RBAC because the MCP server already had it. By the time your incident response team looked at the logs, the agent had enumerated every secret in the production namespace.
This is not a hypothetical. KubeCon EU 2026 identified Shadow MCP deployments - developers connecting unmanaged MCP servers to production databases from their laptops - as a “real and present security risk.” The OWASP MCP Top 10 (v0.1 beta) catalogs the threat surface: Privilege Escalation via Scope Creep (MCP02), Tool Poisoning (MCP03), Insufficient Authentication and Authorization (MCP07), and Shadow MCP Servers (MCP09).
Kubernetes gives you the primitives to build a real enforcement architecture for agent workloads. Kyverno, now a CNCF Graduated project as of March 2026, gives you a policy-as-code layer that speaks native Kubernetes YAML with CEL expressions. This post shows how to combine Kyverno admission policies with agentgateway’s External Authorization to build two-layer security for AI agent MCP traffic: control what gets deployed, then control what those deployments can do.
Why AI Agents Need Kubernetes-Native Policy Enforcement
What MCP Traffic Looks Like in a Cluster
When AI agents run as pods, MCP traffic follows a specific pattern. Agent pods make tool calls via JSON-RPC over HTTP/SSE to MCP server pods, which may run in the same namespace or across namespace boundaries. MCP servers expose tools that agents discover via tools/list, then invoke via tools/call with structured parameters.
Without an enforcement layer, any agent that can reach an MCP server can call any tool that server exposes. That gap between “agent can call any tool it discovers” and “agent should only call authorized tools in its assigned scope” is where security incidents happen.
sequenceDiagram
participant A as Agent Pod
participant GW as agentgateway
participant EA as ExtAuthz Service
participant PE as Policy Engine
participant M as MCP Server Pod
A->>GW: tools/call (JSON-RPC)
GW->>EA: authorization check (actor, tool, params)
EA->>PE: evaluate Cedar/OPA rules
PE-->>EA: allow/deny decision
EA-->>GW: decision
alt allowed
GW->>M: forward tool call
M-->>GW: tool result
GW-->>A: tool result
else denied
GW-->>A: 403 Forbidden
end
The MCP tool call flow through agentgateway’s authorization layer. The policy engine evaluates every call against Cedar or OPA rules before it reaches the MCP server.
An optional MCP gateway sits between agents and MCP servers, providing multiplexing, authorization, and observability. This gateway is the critical second enforcement layer - it operates at runtime on MCP wire traffic, which Kubernetes admission control cannot see.
The OWASP MCP Top 10 Risks That Apply
The OWASP MCP Top 10 (v0.1 beta) describes the threat landscape your policies need to address:
- MCP02 (Privilege Escalation via Scope Creep): Agents accumulating tool permissions beyond their intended scope through excessive or overly permissive MCP server configurations
- MCP03 (Tool Poisoning): Adversaries compromising MCP servers that agents depend on, causing agents to execute malicious actions
- MCP07 (Insufficient Authentication and Authorization): Weak or absent identity verification in MCP ecosystems
- MCP09 (Shadow MCP Servers): Unapproved MCP deployments that operate outside organizational security governance
The architecture in this post addresses all four. Kyverno handles MCP09 at admission and MCP03 via image verification. agentgateway handles MCP02 and MCP07 at runtime.
Kyverno in 2026: CNCF Graduated and CEL-Native
Kyverno graduated as a CNCF project on March 24, 2026, at KubeCon EU in Amsterdam. Jim Bugwadia, Kyverno co-creator and Nirmata CEO, noted at graduation that “policy-as-code provides the essential guardrails for autonomous governance” as AI adoption accelerates. The graduation announcement explicitly called out upcoming releases that extend policy enforcement to “support for artificial intelligence and Model Context Protocol (MCP) gateways” as a roadmap item.
The adoption scale validates the operational readiness: LinkedIn enforces Kyverno policies across 230+ clusters with 500,000+ nodes, handling over 20,000 admission requests per minute.
What Changed in Kyverno 1.17
Kyverno 1.17 (February 2026) promoted CEL-based policy types to v1 GA:
| Policy Type | Purpose |
|---|---|
ValidatingPolicy | Block non-compliant resource creation/updates |
MutatingPolicy | Mutate resources at admission (inject defaults, labels) |
GeneratingPolicy | Create downstream resources when triggers fire |
ImageValidatingPolicy | Verify container image signatures and attestations |
DeletingPolicy | Garbage collect resources matching conditions |
These use apiVersion: policies.kyverno.io/v1 - the stable, production-ready API. The legacy kyverno.io/v1 ClusterPolicy API continues to work in 1.17 but is deprecated and scheduled for removal in 1.20 (October 2026). All new agent policies should use the CEL syntax from day one.
How Do You Enforce Admission-Level Security on Agent Pods?
Kyverno’s primary enforcement point is the Kubernetes admission webhook. When agent pods or MCP server pods are created, Kyverno ValidatingPolicy intercepts the request and either allows or denies it before the pod runs.
Blocking Privileged Agent Pods
The first policy blocks any pod labeled as an agent from running with privileged containers. Privileged containers can access the host kernel, bypass namespace isolation, and read host-level secrets - none of which agents need.
apiVersion: policies.kyverno.io/v1
kind: ValidatingPolicy
metadata:
name: deny-privileged-agent-pods
spec:
validationActions:
- Deny
matchConstraints:
resourceRules:
- apiGroups: ['']
apiVersions: ['v1']
operations: [CREATE, UPDATE]
resources: ['pods']
matchConditions:
- name: is-agent-pod
expression: "'agent' in object.metadata.?labels.orValue({})"
validations:
- message: "Agent pods must not run as privileged"
expression: >-
!object.spec.containers.exists(c,
c.?securityContext.?privileged.orValue(false) == true)
The matchConditions CEL expression targets only pods with an agent label, so this policy does not affect other workloads. The validations expression iterates containers and fails if any has privileged: true.
Enforcing Non-Root for MCP Servers
MCP server pods require a separate policy focused on the runAsNonRoot constraint. A root-running MCP server that gets compromised via Tool Poisoning (MCP03) has full filesystem access within the container, making post-exploitation significantly easier.
apiVersion: policies.kyverno.io/v1
kind: ValidatingPolicy
metadata:
name: require-nonroot-mcp-servers
spec:
validationActions:
- Deny
matchConstraints:
resourceRules:
- apiGroups: ['']
apiVersions: ['v1']
operations: [CREATE, UPDATE]
resources: ['pods']
matchConditions:
- name: is-mcp-server
expression: "'mcp-server' in object.metadata.?labels.orValue({})"
validations:
- message: "MCP server pods must run as non-root"
expression: "object.spec.?securityContext.?runAsNonRoot.orValue(false) == true"
Install Kyverno via Helm before applying these policies:
helm repo add kyverno https://kyverno.github.io/kyverno/
helm repo update
helm install kyverno kyverno/kyverno -n kyverno --create-namespace
Apply both policies and test with a pod that violates them:
kubectl apply -f deny-privileged-agent-pods.yaml
kubectl apply -f require-nonroot-mcp-servers.yaml
# This pod creation should be denied:
kubectl run test-agent --image=nginx \
--labels=agent=true \
--overrides='{"spec":{"containers":[{"name":"test-agent","image":"nginx","securityContext":{"privileged":true}}]}}'
# Error from server: admission webhook denied the request
How Do You Authorize MCP Tool Calls at the Gateway Layer?
Kyverno secures what gets deployed. Once pods are running, you need a second enforcement layer for what they do: which MCP tools they can call, with which parameters, on behalf of which principal.
agentgateway, maintained by Solo.io and donated to the Linux Foundation, is built in Rust with deep MCP and A2A protocol awareness. It supports three authorization models: native Cedar policies, the MCP spec’s own authorization framework, and External Authorization (ExtAuthz) via the Envoy gRPC API.
agentgateway Architecture and ExtAuthz
agentgateway acts as a protocol-aware reverse proxy between agent pods and MCP servers. Critically, it also supports MCP server multiplexing: combining multiple MCP servers into a single composite backend. This makes the gateway the single enforcement point for all agent-to-tool communication, removing the possibility of agents bypassing authorization by connecting directly to MCP servers.
Configure ExtAuthz to send every MCP tool call to an external policy engine before forwarding it:
extAuthz:
host: localhost:9000
protocol:
grpc:
metadata:
dev.agentgateway.jwt: '{"claims": jwt}'
The ExtAuthz service receives the full request context: headers, body (configurable, default 8192 bytes), request path, and custom metadata via CEL expressions. For MCP traffic, the body includes the tool name and parameters from the tools/call request - exactly what your policy engine needs to make an allow/deny decision.
agentgateway is API-compatible with the Envoy External Authorization gRPC service. This means you can wire it to OPA, an existing Envoy ExtAuth implementation, or a custom service without learning a new protocol.
Cedar Policies for Fine-Grained Tool Access
For native Cedar authorization, agentgateway evaluates Cedar policies that express fine-grained tool access rules. A policy granting a specific Kubernetes ServiceAccount access to read-only database tools but not write tools looks like:
permit (
principal == k8s:ServiceAccount::"agent-ns/read-agent",
action == mcp:Action::"tools/call",
resource == mcp:Tool::"database/query"
);
forbid (
principal == k8s:ServiceAccount::"agent-ns/read-agent",
action == mcp:Action::"tools/call",
resource == mcp:Tool::"database/exec"
);
The agent’s Kubernetes ServiceAccount identity flows through the JWT metadata in the ExtAuthz request, giving the policy engine a cryptographically verifiable principal to authorize against.
How Do You Isolate Agent Namespaces in Multi-Tenant Deployments?
In a multi-tenant cluster, agents in one tenant’s namespace must not be able to reach MCP tools in another tenant’s namespace. This requires two layers: network-level isolation and MCP session scoping.
Auto-Generating NetworkPolicy with Kyverno
Kyverno’s GeneratingPolicy creates downstream resources when triggers fire. Use it to automatically generate NetworkPolicy whenever a new agent tenant namespace is provisioned:
apiVersion: policies.kyverno.io/v1
kind: GeneratingPolicy
metadata:
name: generate-agent-network-isolation
spec:
matchConstraints:
resourceRules:
- apiGroups: ['']
apiVersions: ['v1']
operations: [CREATE]
resources: ['namespaces']
matchConditions:
- name: is-agent-namespace
expression: "'agent-tenant' in object.metadata.?labels.orValue({})"
generateRules:
- apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
name: deny-cross-namespace-mcp
namespace: "{{ request.object.metadata.name }}"
data:
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: agentgateway
ports:
- protocol: TCP
port: 8080
Note: This GeneratingPolicy YAML is a structural illustration based on the documented API. Verify it in a non-production cluster before deploying - the Kyverno docs do not yet include MCP-specific GeneratingPolicy examples.
Label namespaces to trigger auto-provisioning:
kubectl create namespace team-a
kubectl label namespace team-a agent-tenant=true
# Kyverno GeneratingPolicy fires -> NetworkPolicy created automatically
kubectl get networkpolicy -n team-a
# NAME POD-SELECTOR AGE
# deny-cross-namespace-mcp <none> 2s
Every new agent tenant namespace gets network isolation automatically, with egress allowed only to the agentgateway namespace on the MCP port.
graph TD
NS_CREATE[Namespace Created\nwith label agent-tenant=true] --> KYVERNO[Kyverno GeneratingPolicy\nFires]
KYVERNO --> NP[NetworkPolicy Created\nin new namespace]
NP --> RULES{Rules}
RULES --> DENY[Default Deny\nAll Ingress/Egress]
RULES --> ALLOW[Allow Egress to\nagentgateway:8080 only]
subgraph Team A Namespace
AGENT_A[Agent Pod A]
end
subgraph Team B Namespace
AGENT_B[Agent Pod B]
end
subgraph agentgateway Namespace
GW[agentgateway]
end
AGENT_A -->|allowed| GW
AGENT_B -->|allowed| GW
AGENT_A -.->|blocked| AGENT_B
style DENY fill:#ff6b6b,color:#fff
style ALLOW fill:#51cf66,color:#fff
Kyverno’s GeneratingPolicy creates NetworkPolicy on namespace creation. Agent pods can only reach the agentgateway - direct cross-tenant access is blocked at the network layer.
Binding MCP Sessions to Namespace Scope
Network isolation handles the routing layer. At the protocol level, each agent’s MCP session is bound to specific namespaces in agentgateway’s routing configuration, so even if network isolation had a gap, the gateway would not forward tool calls to out-of-scope MCP servers. Cross-tenant access is prevented at the protocol level, not just at the reasoning level where the agent is making decisions.
How Do You Detect and Block Shadow MCP Servers on Kubernetes?
The Shadow MCP problem (OWASP MCP09) is concrete: a developer adds an MCP server pod to a production namespace that exposes a database_query tool pointed at the production database. It has no mcp-server label, no image signing, and bypasses your gateway entirely because it was not provisioned through your standard workflow.
Admission Policies That Block Unauthorized MCP Deployments
Kyverno can require that any pod exposing standard MCP server ports (3000, 8080) carries an approved label and a signed image. Here is the detection logic as a Kyverno ValidatingPolicy:
apiVersion: policies.kyverno.io/v1
kind: ValidatingPolicy
metadata:
name: require-approved-mcp-servers
spec:
validationActions:
- Deny
matchConstraints:
resourceRules:
- apiGroups: ['']
apiVersions: ['v1']
operations: [CREATE, UPDATE]
resources: ['pods']
matchConditions:
- name: exposes-mcp-ports
expression: >-
object.spec.containers.exists(c,
c.?ports.orValue([]).exists(p,
p.containerPort == 3000 || p.containerPort == 8080))
validations:
- message: "Pods exposing MCP server ports must carry the approved mcp-server label"
expression: "'mcp-server' in object.metadata.?labels.orValue({})"
Scoping note: Scope this policy to agent-managed namespaces rather than applying it cluster-wide. Ports 3000 and 8080 are common for general HTTP workloads (Spring Boot defaults, Node.js servers, many API services) - a cluster-wide policy would require all such services to carry the
mcp-serverlabel or be denied. Apply this via a namespace labelmatchConditionor restrict thematchConstraintsto specific namespaces where MCP workloads run.
Image signing verification uses Kyverno’s ImageValidatingPolicy to reject MCP server containers that were not signed by your approved key. This blocks Tool Poisoning (MCP03) attacks where a compromised image is pushed under a legitimate name.
flowchart TD
REQ[Pod CREATE Request] --> WEBHOOK[Kyverno Admission Webhook]
WEBHOOK --> CHECK1{Exposes MCP\nserver ports?}
CHECK1 -->|No| ALLOW[Allow Pod]
CHECK1 -->|Yes| CHECK2{Has approved\nmcp-server label?}
CHECK2 -->|No| DENY1[Deny - Shadow MCP\nBlocked]
CHECK2 -->|Yes| CHECK3{Image signed\nby trusted key?}
CHECK3 -->|Yes| ALLOW
CHECK3 -->|No| DENY2[Deny - Unsigned\nMCP Server Image]
DENY1 --> PR[PolicyReport\nViolation Entry]
DENY2 --> PR
PR --> KYVMCP[kyverno-mcp\nshow_violations]
style DENY1 fill:#ff6b6b,color:#fff
style DENY2 fill:#ff6b6b,color:#fff
style ALLOW fill:#51cf66,color:#fff
Every pod exposing MCP ports is intercepted. Without an approved label and signed image, it is denied at admission. Violations are written to PolicyReport for monitoring.
Background Scans and PolicyReport Monitoring
Kyverno’s background scan mode evaluates existing resources against policies on a schedule, catching non-compliant deployments that predate your policies or were deployed during a window when Kyverno was unavailable.
# List all policy violations across namespaces
kubectl get policyreport -A
# Get details on a specific report
kubectl describe policyreport -n production
# Check cluster-wide violations
kubectl describe clusterpolicyreport
PolicyReport resources follow the Kubernetes Policy WG standard format, making them compatible with any tool that reads them - including the kyverno-mcp server.
The kyverno-mcp Feedback Loop
Nirmata’s kyverno-mcp server (GitHub: nirmata/kyverno-mcp) implements the Model Context Protocol to let AI assistants manage Kyverno policies and read violation reports. This creates a useful feedback loop: AI agents are governed by Kyverno policies, and those same agents (or AI assistants with cluster access) can query the policy status via the kyverno-mcp server.
The server operates in two modes: proactive assessment (scanning cluster resources against policies using kyverno apply without requiring Kyverno to be installed) and violation monitoring (reading existing PolicyReport/ClusterPolicyReport resources from a deployed Kyverno instance).
Exposed MCP tools:
list_contexts/switch_context- Multi-cluster context managementapply_policies- Scan against curated sets:pod-security,rbac-best-practices,kubernetes-best-practices, orallshow_violations- Read policy violations with severity levels and timestamps
Install and configure:
brew tap nirmata/tap
brew install kyverno-mcp
Add to your MCP client configuration:
{
"mcpServers": {
"kyverno": {
"command": "/path/to/kyverno-mcp",
"args": ["--kubeconfig=/path/to/kubeconfig"]
}
}
}
Run with HTTPS for production use:
./kyverno-mcp --http-addr :8443 \
--tls-cert /path/to/cert.pem \
--tls-key /path/to/key.pem
Now your AI assistant can ask “which agent namespaces have policy violations?” and get a structured answer from PolicyReport resources - without needing direct kubectl access.
Putting It Together: Reference Architecture
The full security architecture composes three enforcement layers:
graph TB
subgraph "Admission Layer (Kyverno)"
VAP[ValidatingPolicy\nDeny privileged pods\nRequire non-root\nBlock Shadow MCP]
IVP[ImageValidatingPolicy\nVerify MCP server\nimage signatures]
GP[GeneratingPolicy\nAuto-create NetworkPolicy\non namespace provisioning]
end
subgraph "Team A Namespace"
AGENT_A[Agent Pod\nlabel: agent=true]
end
subgraph "Team B Namespace"
AGENT_B[Agent Pod\nlabel: agent=true]
end
subgraph "Gateway Layer (agentgateway)"
GW[agentgateway\nMCP multiplexer]
EA[ExtAuthz Service\nCedar / OPA policies]
end
subgraph "MCP Servers Namespace"
MCP1[MCP Server\nDatabase tools]
MCP2[MCP Server\nKubectl tools]
end
subgraph "Monitoring"
PR[PolicyReport\nClusterPolicyReport]
KYVMCP[kyverno-mcp server\nshow_violations]
end
POD_CREATE[Pod CREATE request] --> VAP
POD_CREATE --> IVP
VAP -->|allow| AGENT_A
VAP -->|allow| AGENT_B
AGENT_A -->|tools/call| GW
AGENT_B -->|tools/call| GW
GW --> EA
EA -->|allow| MCP1
EA -->|allow| MCP2
EA -.->|deny| DENIED[Denied Tool Calls]
VAP --> PR
GW --> PR
PR --> KYVMCP
GP -->|generates| NP[NetworkPolicy\nper namespace]
NP -.->|blocks| CROSS[Cross-namespace\ndirect access]
style VAP fill:#4c9be8,color:#fff
style GW fill:#4c9be8,color:#fff
style PR fill:#74c0fc,color:#333
style DENIED fill:#ff6b6b,color:#fff
Two enforcement planes: Kyverno at admission controls what gets deployed, agentgateway at runtime controls what those deployments can do. PolicyReports feed both the monitoring layer and kyverno-mcp for AI-assisted compliance queries.
The architecture separates concerns cleanly:
- Kyverno (admission plane): Controls pod security posture, blocks Shadow MCP, auto-provisions namespace isolation, verifies image signatures. Fires once per resource lifecycle change.
- agentgateway (runtime plane): Authorizes every MCP tool call against Cedar or OPA policies, using the agent’s ServiceAccount identity as the principal. Fires on every
tools/callrequest. - PolicyReport (monitoring plane): Aggregates violations from both layers. kyverno-mcp makes this queryable via MCP from AI assistants.
Note that agentgateway (Solo.io, Linux Foundation) and kagent (CNCF Sandbox) are separate projects with different governance. kagent provides Kubernetes CRDs for defining agents and managing their lifecycle via a controller - it complements this architecture by giving you a standardized way to provision agent workloads that Kyverno policies then enforce.
Frequently Asked Questions
Can Kyverno directly intercept MCP protocol traffic between agents and tools?
No. Kyverno operates at the Kubernetes admission control layer - it evaluates API requests to create, update, or delete Kubernetes resources like pods. It does not inspect MCP wire protocol traffic at runtime. For runtime MCP traffic authorization, use an MCP-aware gateway like agentgateway with External Authorization. The two layers are complementary: Kyverno secures what gets deployed; the gateway secures what those deployments do at runtime.
What is a Shadow MCP server and how does Kyverno help prevent them?
A Shadow MCP server (OWASP MCP09) is an unapproved MCP server deployed outside your organization’s security governance - the most common pattern is developers connecting personal AI assistants to production databases. Kyverno prevents Shadow MCP at the cluster level by using admission policies that block pods exposing MCP server ports unless they carry approved labels and use signed container images. Background policy scans detect any existing non-compliant deployments and write them to PolicyReport resources, where they are surfaced by kyverno-mcp’s show_violations tool.
Should I use Kyverno or OPA for AI agent policy enforcement on Kubernetes?
Both work for different layers. Kyverno uses standard Kubernetes YAML with CEL expressions and no Rego requirement, making it faster to adopt for admission-level pod security policies. OPA’s Rego is more expressive for complex gateway authorization logic like evaluating MCP tool call parameters. agentgateway supports both via its ExtAuthz interface, plus Cedar for native policies. Most teams end up using Kyverno at admission and OPA or Cedar at the gateway layer - you do not have to choose one for everything.
How do I monitor Kyverno policy violations for my agent workloads?
Kyverno writes violations to PolicyReport (namespaced) and ClusterPolicyReport (cluster-wide) custom resources. Use kubectl get policyreport -A to list them. For AI-assisted monitoring, the kyverno-mcp server’s show_violations tool reads these reports through the MCP protocol, enabling AI assistants to query violation status across clusters without direct kubectl access. For GitOps workflows, Kyverno in Enforce mode triggers ArgoCD sync failures for non-compliant resources, making violations visible in the ArgoCD UI.
What Kyverno API should I use for agent policies in 2026?
Use policies.kyverno.io/v1 with the CEL-based types: ValidatingPolicy, MutatingPolicy, GeneratingPolicy, ImageValidatingPolicy. These were promoted to v1 GA in Kyverno 1.17 (February 2026). The legacy kyverno.io/v1 ClusterPolicy API continues to work in 1.17 but is deprecated and scheduled for removal in 1.20 (October 2026). Start new agent policies on the CEL syntax - migrating away from ClusterPolicy later is extra work you do not need.