Kyverno policies for AI agents

Kyverno policies for AI agents are admission rules that check the pod spec of each agent before the Kubernetes API server accepts it. Write them as ValidatingPolicy resources, enforce the Pod Security Standards Restricted controls on agent pods, and turn on background evaluation to find pods that already run. Kyverno’s ClusterPolicy type is deprecated from v1.19.
For the wider picture, see governing AI agents. This post covers the Kubernetes side only.
What Kyverno checks for an agent pod
An AI agent on Kubernetes is usually a pod that runs a model client, a tool runner and sometimes a shell. Kyverno can check that pod spec in three places. The Kyverno overview describes it as a policy engine that works as a Kubernetes admission controller, as a CLI-based scanner, and at runtime. It can validate, mutate, generate or clean up resources. This post uses validation, because an unsafe agent pod should be refused, not quietly changed.
| Check point | Setting that controls it | What happens on a failure |
|---|---|---|
| Admission | evaluation.admission.enabled in the policy, with validationActions set to Deny |
The create or update is rejected |
| Background scan | evaluation.background.enabled: true |
The result goes to a PolicyReport. Nothing is rejected |
| Pipeline | kyverno apply against manifest files, run in CI |
The command reports the result before the manifest reaches a cluster |
The admission and background rows run inside the cluster. The pipeline row catches the problem earlier, before a commit is applied. Use the pipeline check and the admission check together, so an agent manifest that skips CI still hits the webhook.
The Restricted controls to enforce on agent pods
The Kubernetes Pod Security Standards define three profiles: Privileged, Baseline and Restricted. Restricted is aimed at current pod hardening best practices, and it is the right baseline for code you did not write line by line. An agent that runs tool calls is that kind of code. Treat each agent pod as lower trust.
The table lists the Restricted controls that matter most for agent pods, with the field each one checks and the value it allows.
| Control | Field | Allowed value |
|---|---|---|
| Privilege escalation | spec.containers[*].securityContext.allowPrivilegeEscalation |
false |
| Non-root user | spec.securityContext.runAsNonRoot, or the same field on each container |
true |
| Seccomp | spec.securityContext.seccompProfile.type, or the same field on each container |
RuntimeDefault or Localhost |
| Capabilities | spec.containers[*].securityContext.capabilities.drop and .add |
drop includes ALL. add is undefined or NET_BIND_SERVICE only |
| HostPath volumes | spec.volumes[*].hostPath |
Not set. This control comes from Baseline and Restricted inherits it |
Restricted also limits volume types to configMap, csi, downwardAPI, emptyDir, ephemeral, persistentVolumeClaim, projected and secret. The sample policy below checks privilege escalation, non-root, capabilities and HostPath, on containers only. The standard applies the same rules to initContainers and ephemeralContainers, so add those lists before you depend on the policy.
Why ClusterPolicy is the wrong starting point
Many Kyverno examples still use ClusterPolicy. The Kyverno docs say ClusterPolicy and Policy are deprecated as of Kyverno v1.19 and will be removed in v1.20. They also say the CEL-based policy types provide full feature parity. New agent rules should start from ValidatingPolicy.
| Point | ClusterPolicy | ValidatingPolicy |
|---|---|---|
| Status in the v1.19 docs | Deprecated. Removed in v1.20 | Stable |
| Relationship to Kubernetes | Kyverno’s own policy resource | A superset of the Kubernetes ValidatingAdmissionPolicy type |
| Namespaced variant | Policy (deprecated) |
NamespacedValidatingPolicy, which a namespace owner can manage without cluster-admin rights |
| Results | PolicyReports | PolicyReports, with optional auditAnnotations |
The ValidatingPolicy type is the one the docs describe for admission, background scans and reporting, so the rest of this post uses it.
Write the agent policy as a ValidatingPolicy
Follow these steps to write the sample policy. It applies only to pods that carry a label you choose, so ordinary workloads in the same cluster are not affected.
- Label every agent pod with one key and value. The sample uses
component: ai-agent. Pick a label your platform team can enforce in the Deployment template. - Set
matchConstraints.resourceRulesto pods in the core API group, forCREATEandUPDATE. UsematchConditionsto narrow the match to the agent label. The docs showmatchConditionsas a list ofnameandexpressionpairs. - Set
validationActionstoDenyafter the Audit run is clean. Start withAuditon a live cluster (see the FAQ). - Add one entry under
validationsfor each control. Each entry has anexpressionand amessage.
apiVersion: policies.kyverno.io/v1
kind: ValidatingPolicy
metadata:
name: ai-agent-pod-restricted
spec:
validationActions:
- Deny # Start with Audit on a live cluster; switch to Deny after the Audit run is clean
matchConstraints:
resourceRules:
- apiGroups: ['']
apiVersions: [v1]
operations: [CREATE, UPDATE]
resources: [pods]
matchConditions:
- name: is-ai-agent
expression: "has(object.metadata.labels) && 'component' in object.metadata.labels && object.metadata.labels['component'] == 'ai-agent'"
validations:
- expression: "object.spec.containers.all(c, has(c.securityContext) && has(c.securityContext.allowPrivilegeEscalation) && c.securityContext.allowPrivilegeEscalation == false)"
message: "Every container in an AI agent pod must set allowPrivilegeEscalation to false."
- expression: "object.spec.containers.all(c, !has(c.securityContext) || !has(c.securityContext.runAsNonRoot) || c.securityContext.runAsNonRoot == true) && ((has(object.spec.securityContext) && has(object.spec.securityContext.runAsNonRoot) && object.spec.securityContext.runAsNonRoot == true) || object.spec.containers.all(c, has(c.securityContext) && has(c.securityContext.runAsNonRoot) && c.securityContext.runAsNonRoot == true))"
message: "AI agent pods must run as a non-root user. No container may set runAsNonRoot to false, even when the pod sets it to true."
- expression: "object.spec.containers.all(c, has(c.securityContext) && has(c.securityContext.capabilities) && has(c.securityContext.capabilities.drop) && c.securityContext.capabilities.drop.exists(x, x == 'ALL') && (!has(c.securityContext.capabilities.add) || c.securityContext.capabilities.add.all(x, x == 'NET_BIND_SERVICE')))"
message: "Every container in an AI agent pod must drop ALL capabilities and may add back only NET_BIND_SERVICE."
- expression: "!has(object.spec.volumes) || object.spec.volumes.all(v, !has(v.hostPath))"
message: "AI agent pods must not mount hostPath volumes."
The non-root check accepts a pod-level or container-level setting, as the Restricted table allows, but fails any container that sets runAsNonRoot: false, even when the pod sets it to true. The capabilities check rejects any add entry other than NET_BIND_SERVICE. The sample does not check seccomp or runAsUser. Add those with the same pattern before you rely on the policy.
Test the policy with the Kyverno CLI
Test the policy against two manifests before it reaches a cluster: one that breaks a control and one that meets all four. Keep both files in the repository next to the policy, so CI can run them on every change.
- Save an agent pod manifest with the label
component: ai-agentthat setsallowPrivilegeEscalation: true. Call itagent-pod-bad.yaml. - Save a compliant pod manifest with the same label and every control set. Call it
agent-pod-good.yaml. - Run
kyverno applyon the policy and each manifest. Add--detailed-resultsto see each rule’s outcome. - Confirm the bad manifest fails and the good one passes. Then add the same two commands to your pipeline.
kyverno apply ai-agent-pod-restricted.yaml --resource agent-pod-bad.yaml --detailed-results
kyverno apply ai-agent-pod-restricted.yaml --resource agent-pod-good.yaml --detailed-results
Check that the CLI result for the bad manifest matches what the admission webhook returns on a test cluster. Use the CLI for fast feedback in CI and the webhook for enforcement.
Enforcing this with Kyverno
Kyverno is open source, and the Kyverno project page covers installation and the project’s docs. The rollout order for this policy is the same as for any Kyverno rule. Start with Audit, which records results in PolicyReports instead of rejecting requests. Read the reports, fix the agent specs that break a control, and add exceptions only for workloads that need one. Then change validationActions to Deny.
Exceptions use a separate resource. The Kyverno docs say a PolicyException lets select resources bypass a policy without changing the policy itself. Keep exceptions narrow and name the workload, so the rule still applies to every other agent pod.
Two related posts cover the next layers. Governance beyond the admission webhook explains what admission rules cannot see. Policy-driven authorization for AI agents with Kyverno and AWS AgentCore shows the same policy idea applied to agent tool calls.
Checked against the Kyverno v1.19 docs and the Kubernetes Pod Security Standards, as of 2026-11-04.
Frequently asked questions
Can Kyverno stop an AI agent from starting an unsafe pod?
Yes, for the pod spec. Kyverno receives validating admission webhook calls from the Kubernetes API server, so a ValidatingPolicy with Deny rejects a create or update that breaks a rule. It does not reach into a process that is already running inside an allowed pod. Pair admission rules with limits on what the agent's service account can do, and with runtime controls for the actions a pod spec cannot describe.
Should new agent policies use ClusterPolicy?
No. The Kyverno docs mark ClusterPolicy and Policy as deprecated from v1.19 and removed in v1.20, and they say the CEL-based policy types have full feature parity. Write new rules as ValidatingPolicy. Use NamespacedValidatingPolicy when a namespace owner manages the rule. Convert any existing ClusterPolicy resources with the migration guide before you upgrade to v1.20.
What is the difference between Audit and Deny in a ValidatingPolicy?
Deny rejects the request, so the object is not created or updated. Audit records the result in PolicyReports instead of rejecting the request. Run a new agent policy with Audit on a live cluster, read the reports, fix the workloads or add exceptions, and then switch the rule to Deny. Switching early blocks deployments that were already working.
Does Kyverno check agent pods that are already running?
Yes, when background evaluation is on. The ValidatingPolicy docs describe background scans that run periodically or when a policy is created or changed, and they write the results to PolicyReports. A background result does not reject a pod. It shows you which running pods break the rule so you can fix them on their next rollout.