Dosu LogoDosu Logo
Ask
Join our Discord
kyvernoPublic
Nirmata
Documentskyverno
CEL Policy Exception Handling
CEL Policy Exception Handling
Type
Topic
Status
Published
Created
Aug 8, 2026
Updated
Aug 8, 2026
Created by
Dosu Bot
Updated by
Dosu Bot

CEL Policy Exception Handling#

Kyverno's CEL-based policy engines — vpol (ValidatingPolicy/NamespacedValidatingPolicy) and ivpol (ImageValidatingPolicy/NamespacedImageValidatingPolicy) — compile PolicyException objects into each policy at reconcile time, not at evaluation time. A compiled policy already embeds its exceptions; the engine checks for skips during evaluation by inspecting the compiled artifact .


How Exceptions Are Compiled In#

Static provider (NewProvider) — used in CLI and background scan pipelines. Both vpol and ivpol static providers iterate over all policies, match exceptions by PolicyRef.Name/Kind, and call compiler.Compile(policy, matchedExceptions) immediately .

Dynamic (Kubernetes) provider (NewKubeProvider) — used by the admission controller. Both vpol and ivpol providers spin up controller-runtime controllers that watch policy CRDs. When polexEnabled is true, each provider also registers a handler.Funcs watcher on PolicyException objects against the controller-runtime manager's cache .

On any PolicyException create/update/delete event, the handler extracts polex.Spec.PolicyRefs, resolves the referenced policy names, and enqueues reconcile.Request items for those policies — dropping the exception object itself .

During reconciliation, both reconcilers call engine.ListExceptions to enumerate matching exceptions:

engine.ListExceptions(r.polexLister, policy.GetKind(), policy.GetName())

ListExceptions lists from a PolicyExceptionLister interface backed by a client-go SharedInformerFactory lister — not the controller-runtime manager cache that fired the event . The lister is scoped to a namespace when ExceptionNamespace is set .

After fetching exceptions, the vpol reconciler calls r.compiler.Compile(policy, exceptions) and stores the result in r.policies[key] under a sync.RWMutex . The ivpol reconciler stores exceptions directly on the Policy struct (no compilation step) alongside autogen variants .

Fetch() on both reconcilers returns the cached compiled policies under a read lock .


Wiring in main.go#

The exception lister is constructed once at startup from the kyvernoInformer client-go factory and passed to all three CEL providers :

celExceptionLister := celengine.NewPolicyExceptionLister(
    kyvernoInformer.Policies().V1beta1().PolicyExceptions().Lister(),
    internal.ExceptionNamespace(),
)

The controller-runtime manager's cache is synced at startup via mgr.GetCache().WaitForCacheSync(ctx) , but this is independent of the client-go informer factory sync for PolicyException. Both informers watch the same PolicyException resource with different resync periods, and neither is guaranteed to deliver events in the same order.


Race Condition: Dual-Informer Desync (Issue #16989)#

Root cause: The controller-runtime cache and the client-go SharedInformerFactory maintain two independent watch streams on PolicyException. When the manager cache delivers a create/update event first, the reconciler reads from r.polexLister (client-go factory) before that lister has received the same object. The result: exceptions are empty, the policy compiles without the exception, the cache is updated, and reconciliation returns success with no requeue .

manager cache ──▶ event arrives ──▶ enqueue policy reconcile
client-go lister (still stale) ──▶ ListExceptions returns []
                                   ──▶ Compile(policy, []) cached ──▶ no requeue

Impact in HA deployments: Each admission replica resolves the race independently. A replica that compiled the policy without exceptions permanently ignores the PolicyException for its lifetime — it never detects the inconsistency, logs no error, and reports healthy. Two identical AdmissionReview requests routed to different replicas can return allowed: true and allowed: false .

The two watch streams are distinguishable in logs by their resync periods:

  • resyncPeriod=15m0s → client-go factory lister
  • resyncPeriod=10h45m… → controller-runtime manager cache

Scope: Affects vpol and ivpol engines in steady-state (not just startup). The mpol engine uses the same celExceptionLister pattern and is similarly exposed.


Related Known Issues#

IssueDescription
#16989Active bug — dual-informer race causing PolicyException to be permanently ignored per-replica
#16952PolicyException.matchConditions not rewritten for autogen targets (separate defect, same code path)
#16281Startup race — policies ignored on webhook start before initial reconcile completes; fixed in PR #16434

Key Source Files#

FileRole
pkg/cel/engine/exception.goPolicyExceptionLister interface, NewPolicyExceptionLister, ListExceptions
pkg/cel/policies/vpol/engine/provider.goNewProvider (static) and NewKubeProvider (dynamic) for vpol; exception watcher registration
pkg/cel/policies/ivpol/engine/provider.goSame pattern for ivpol
pkg/cel/policies/vpol/engine/reconciler.govpol reconciler: fetches exceptions via lister, compiles, stores under RWMutex
pkg/cel/policies/ivpol/engine/reconciler.goivpol reconciler: same pattern, stores raw exceptions (not compiled) on Policy struct
cmd/kyverno/main.goWiring: celExceptionLister construction and provider setup
Documents
Admission Policy CEL Evaluation
API Call Execution
API Call Response Size Enforcement
Background Controller Trigger Validation
Background Controller UpdateRequest Processing
Background Mutation Engine
Background Scan Report Reconciliation
CEL Context Injection
CEL Policy Exception Handling
CLI Policy Result Processing
CLI Policy Testing
CLI Resource Resolution
CLI Worker Pool Management
Concurrency Safety
Engine Context Propagation
Generate Policy UpdateRequest Lifecycle
GeneratingPolicy Downstream Cleanup
GeneratingPolicy Synchronization and Reconciliation
Git URL Parsing
Image Verification CEL Path
ImageValidatingPolicy Webhook Architecture
JMESPath Type Safety
JSON Patch Mutation
MutatingPolicy CEL Engine Namespace Resolution
MutatingPolicy Resource Targeting
Namespaced Image Validating Policy
NamespaceSelector Policy Enforcement
OCI Referrers API Fallback
Policy Controller Reconciliation
Projected Service Account Token
Prometheus Metrics Integration
Registry Authentication
Report Controller Goroutine Lifecycle
Sigstore & TUF Integration
Strategic Merge Patch
TTL Controller Lifecycle
ValidatingPolicy Autogen
ValidatingPolicy Engine
ValidatingPolicy Status Management
Variable Substitution
Webhook Generation
Webhook Lifecycle Management
Webhook Selector Grouping