Kyverno's v1.17 release announcement carried a four-row deprecation table: ClusterPolicy and CleanupPolicy deprecated at v1.17 (January 2026), critical fixes only through v1.18 (April 2026) and v1.19 (July 2026), removal planned in v1.20 (October 2026). As of 16 October 2026 the newest published tag on the kyverno/kyverno releases page is v1.19.1 from 10 September, with v1.19.2-rc.1 cut on 15 October, so the removal release has not landed. Removal releases do execute on schedule, as anyone who met Kubernetes 1.37 dropping 18 kubelet flags already knows.

Two things make this worse than a rename. Kyverno's migration guide lists validate.podSecurity as not supported in the CEL policy types, and that field is how most fleets enforce Pod Security Standards. The converted upstream library also disagrees with Pod Security Admission today: a drift issue filed against kyverno/policies on 14 September 2026 is still open, and it documents a restricted-profile Pod that the new policy admits and PSA denies. Each tip below names the artifact and the command.

The tips

  1. Read the running image tag, not the Helm chart version. A chart pin and a container tag drift apart quietly, and the version that decides whether your policies still load is the one in the pod spec. Same failure shape as containerd 1.x against a 1.38 kubelet, where the pinned manifest and the installed binary disagreed. v1.18 and v1.19 accept legacy policies with warnings; v1.20 is where that stops.
   kubectl -n kyverno get deploy -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.template.spec.containers[0].image}{"\n"}{end}'
  1. Qualify every inventory query with the API group, because the names collide. The legacy kinds are ClusterPolicy and Policy in group kyverno.io. The replacements live in a group named policies.kyverno.io, so a bare kubectl get policy is ambiguous. Build the full list before you plan anything, the same discipline that keeps a kube-proxy IPVS to nftables move from turning into a discovery exercise mid-cutover.
   kubectl get clusterpolicy.kyverno.io -o name
   kubectl get policy.kyverno.io -A -o name
   kubectl api-resources --api-group=policies.kyverno.io
  1. Count your validate.podSecurity rules first: that set cannot be converted mechanically. The migration guide's answer for the field is "Not supported; use spec.validations for each pod security control," which turns one rule into one hand-written CEL expression per control. The count below is the real size of the project.
   kubectl get clusterpolicy.kyverno.io -o json \
     | jq -r '[.items[] | select(any(.spec.rules[]?; .validate.podSecurity != null)) | .metadata.name] | "podSecurity policies: \(length)\n\(.[])"'
  1. Grade converted policies against PSA, not against the policy they replaced. Two Kyverno policies agreeing with each other proves nothing about the standard you attest to. Label a staging namespace so the in-tree controller reports its own verdict without blocking:
   kubectl label ns kyverno-migration-test \
     pod-security.kubernetes.io/warn=restricted \
     pod-security.kubernetes.io/audit=restricted

Per the Pod Security Admission docs, warn returns a warning on the API response and audit writes a pod-security.kubernetes.io/audit-violations annotation onto the audit event. Any fixture where PSA warns and the Kyverno report passes is drift you are about to ship.

fixture Podno seccompProfilePSA warn=restrictedValidatingPolicy reportverdicts agree?drift: hold the cutovercontrol migratedwarningpassnoyes
  1. Test the seccomp case with nothing set anywhere. Issue #1537 states that for the restricted profile the ValidatingPolicy treats an unset profile as RuntimeDefault, so a Pod with no seccomp setting at pod or container level passes where PSA denies it, while the pod-security-cel ClusterPolicy variant was fixed and is correct. The migration makes that control weaker without logging anything. Apply a Pod with no securityContext.seccompProfile and record which component denies it.
  2. Fuzz the capabilities list with an empty string and a duplicate entry. The same issue records three capability defects: Kyverno accepts an empty capability name, so add: [""] passes where PSA denies it at baseline; the restricted exists_one and size() == 1 checks reject duplicate entries that PSA accepts; and the ValidatingPolicy add expression raises a CEL error for any container that adds a capability. A CEL evaluation error is not a clean deny, so set failurePolicy deliberately and confirm where an erroring expression lands in your cluster. That decision is the one covered in stopping Kubernetes admission control from failing open, and it has the same two bad answers here.
  3. If you run Windows nodes, write the spec.os.name guard yourself. PSA exempts Windows Pods from the allowPrivilegeEscalation, capabilities and seccomp checks. Issue #1537 notes Kyverno carries no spec.os.name guard for those controls, so conforming Windows Pods get denied by the converted policy. One query tells you whether this is your problem:
   kubectl get nodes -l kubernetes.io/os=windows -o name
  1. Re-model per-namespace enforcement before failureActionOverrides goes away. The guide lists both validate.failureActionOverrides and validate.allowExistingViolations as unsupported and points at policy exceptions instead. A team running Audit in production and Enforce in a pilot namespace holds that split inside one object today; after conversion it becomes separate policies plus matchConditions. Find every affected policy:
   kubectl get clusterpolicy.kyverno.io -o json \
     | jq -r '.items[] | select(any(.spec.rules[]?; .validate.failureActionOverrides != null or .validate.allowExistingViolations != null)) | .metadata.name'
  1. Build the exception list from today's policy reports, not from memory. With allowExistingViolations gone, a converted policy in Enforce mode starts rejecting updates to workloads that were already violating, so an unrelated kubectl rollout restart fails on a Deployment nobody touched. Harvest current failures and turn them into scoped PolicyException objects before the flip:
   kubectl get polr -A -o json \
     | jq -r '.items[].results[]? | select(.result=="fail") | "\(.policy)\t\(.resources[0].namespace)/\(.resources[0].name)"' | sort -u
  1. Migrate exceptions in the same change as the policies. The guide calls for converting legacy kyverno.io PolicyException objects to policies.kyverno.io. An exception in the old group exempts nothing from a new-group policy, so a policy-only migration re-blocks every workload you previously carved out. List them with the group pinned: kubectl get polex.kyverno.io -A -o name.
  2. Treat the upstream policy library as a draft under test. Issue #1537 identifies the two sets it compares as kyverno.io/v1 ClusterPolicy under pod-security-cel/ and policies.kyverno.io/v1alpha1 ValidatingPolicy under pod-security-vpol/. The converted set still sits on the alpha surface even though v1.17 promoted the policy types to v1, and the issue was last updated on 1 October 2026 with no fix merged. Copy from it, test what you copied.
  3. Fail CI on the deprecation warnings, and trust release notes over the guide's limitation table. The migration guide recommends --warnings-as-errors so a deprecated field breaks the pipeline instead of logging:
    kyverno apply ./policies/ --resource ./tests/fixtures/ --warnings-as-errors
    kyverno test ./policies/ --warnings-as-errors

One caveat on that page: it cites issues #14852 (skipBackgroundRequests for MutatingPolicy and GeneratingPolicy) and #14853 (useServerSideApply for GeneratingPolicy) as open gaps, and both were closed after being opened on 26 January 2026. Check each listed limitation against the notes for the version you install. If the proposal on the table is "move to Gatekeeper instead," price its own moving defaults: Gatekeeper v3.22.0 turned on sync-vap-enforcement-scope by default, which changes which namespaces ValidatingAdmissionPolicy enforces on, and gator test plus gator verify give you the equivalent CI gate.

Wrap-up

Tip 4 is the habit that decides this migration: PSA is the referee. A converted policy that matches the ClusterPolicy it replaced can still disagree with the standard in your control narrative, and issue #1537 is the evidence that the official library disagrees today. Build the equivalence suite before v1.20 ships, one fixture per control, both verdicts recorded, Windows nodes included. That suite plus the exception inventory from tip 9 is what an auditor will accept, and it gets expensive to produce once the removal release is already in your upgrade path.