Documentskyverno
CEL Context Injection
CEL Context Injection
Type
Topic
Status
Published
Created
Jul 9, 2026
Updated
Jul 9, 2026
Created by
Dosu Bot
Updated by
Dosu Bot

CEL Context Injection#

CEL context injection is the mechanism by which Kyverno resolves Kubernetes admission data — namespaceObject, object, oldObject, request, and variables — and makes them available as named variables inside CEL expression evaluation environments. This applies uniformly to ValidatingPolicy, MutatingPolicy, and DeletingPolicy policy types.

The canonical string keys for all context variables are defined in pkg/cel/compiler/keys.go:

CEL VariableKey ConstantResolved From
namespaceObjectNamespaceObjectKeyNamespaceResolver (or resource itself for Namespace kind)
objectObjectKeyadmission.Attributes.GetObject()
oldObjectOldObjectKeyadmission.Attributes.GetOldObject()
requestRequestKeyRaw admissionv1.AdmissionRequest (converted to map[string]any)
variablesVariablesKeyLazily-evaluated compiled variable programs
exceptionsExceptionsKeyMatched PolicyException objects

These constants are shared across all policy type compilers, making the CEL activation map consistent regardless of policy kind.

Injection Pipeline Per Policy Type#

Each policy engine follows the same three-stage pattern: resolve → convert → inject.

ValidatingPolicy#

The engine resolves the namespace object from a NamespaceResolver if the request's namespace is non-empty, then builds a k8s.io/apiserver/pkg/admission.Attributes record . The Attributes record, the raw AdmissionRequest, and the namespace are passed to prepareK8sData, which:

  1. Calls utils.ObjectToResolveVal(namespace) → CEL-compatible value for namespaceObject
  2. Calls utils.ObjectToResolveVal(attr.GetObject()) → value for object
  3. Calls utils.ObjectToResolveVal(attr.GetOldObject()) → value for oldObject
  4. Calls utils.ConvertObjectToUnstructured(request)map[string]any for request

The resulting evaluationData struct is then spread into the CEL activation dataNew map in evaluateWithData.

userInfo is embedded in the request variable (as a field on the admission request map). Prior to PR #15619, vpol and mpol engines passed nil for the user context; the fix wires in admissionpolicy.NewUser(request.Request.UserInfo) when constructing admission.Attributes .

MutatingPolicy#

pkg/cel/policies/mpol/compiler/eval.go:prepareData mirrors prepareK8sData but accepts a typed *corev1.Namespace rather than runtime.Object. It also re-extracts object/oldObject from the raw AdmissionRequest and merges them back into the request map at the "object" and "oldObject" keys , ensuring the request body is self-consistent. The final activation map is returned directly .

namespaceObject support was added in PR #15625; before that fix the variable was absent from the MutatingPolicy CEL context.

DeletingPolicy#

DeletingPolicy's compiled Policy.Evaluate takes unstructured.Unstructured directly (no admission.Attributes). It calls utils.ObjectToResolveVal(namespace) and sets NamespaceObjectKey alongside ObjectKey and ResourceKey in the activation map . The engine resolves the namespace in Handle — namespaced resources use the NamespaceResolver, while cluster-scoped Namespace resources build a synthetic corev1.Namespace from the resource's own labels and then optionally override with the resolver's copy .

namespaceObject support was added in PR #14422.

CEL Environment Declaration#

Before any expression can reference a variable, it must be declared in the CEL environment at compile time. For ValidatingPolicy, this is done in createBaseVpolEnv, which registers all context variables . NamespaceType and RequestType are typed CEL objects (not dyn) so that field access is validated at compile time; object and oldObject use cel.DynType because they represent arbitrary Kubernetes resources.

DeletingPolicy and MutatingPolicy follow the same pattern in their respective compiler.go files. A missing variable declaration at compile time will cause expression compilation errors rather than runtime panics.

Nil Value Handling#

utils.ObjectToResolveVal (from github.com/kyverno/sdk/extensions/cel/utils) handles nil inputs — if the namespace resolver returns nil (e.g., for cluster-scoped resources or cache misses), the resulting CEL value is a null/null-like value rather than a panic. This is critical for namespaceObject, which is nil for cluster-scoped resources (except Namespace kind in DeletingPolicy, which has special synthetic-object logic).

For request, utils.ConvertObjectToUnstructured returns nil when passed a nil pointer; the mpol prepareData guards against this with an explicit if request != nil check before writing to the activation map.

For DELETE operations, attr.GetObject() returns nil and attr.GetOldObject() holds the deleted resource; ObjectToResolveVal(nil) correctly produces a null CEL value in those cases.

Bug Fix History#

Context injection was added incrementally per policy type. Several missing-variable bugs were fixed:

PRPolicy TypeWhat was missingFix
#14422DeletingPolicynamespaceObject not injectedAdded namespace runtime.Object param to Evaluate; injected via utils.ObjectToResolveVal
#14599ValidatingPolicymatchConstraints dropped at compile timeStored matchConstraints in compiled Policy struct; engine uses CompiledPolicy.MatchConstraints()
#15619vpol + mpoluserInfo was nil (TODO placeholder)Wired admissionpolicy.NewUser(request.Request.UserInfo) into admission.Attributes in all engine Handle methods
#15625MutatingPolicynamespaceObject not injectedAdded namespace field + injection in prepareData
#16200DeletingPolicynamespaceSelector ignored for Namespace resources; namespaceObject always nilChanged operation to admission.Create so matcher reads labels correctly; synthetic corev1.Namespace built from resource labels for cluster-scoped Namespace kind

The pattern across fixes is consistent: the engine layer resolves the namespace object; the compiler/eval layer converts and injects it; missing variables cause silent nil or evaluation failures at runtime, not compile time.

Key Source Files#

FilePurpose
pkg/cel/compiler/keys.goAll context variable key constants (NamespaceObjectKey, ObjectKey, etc.)
pkg/cel/policies/vpol/compiler/eval.goprepareK8sData — converts admission attributes for ValidatingPolicy
pkg/cel/policies/vpol/compiler/policy.goevaluateWithData — builds CEL activation map and runs validations
pkg/cel/policies/vpol/compiler/compiler.gocreateBaseVpolEnv — declares CEL variables in the environment
pkg/cel/policies/vpol/engine/engine.govpol engine: resolves namespace, builds admission.Attributes, calls Evaluate
pkg/cel/policies/mpol/compiler/eval.goprepareData — converts admission attributes for MutatingPolicy
pkg/cel/policies/dpol/compiler/policy.goDeletingPolicy Evaluate — builds activation map from unstructured.Unstructured
pkg/cel/policies/dpol/engine/engine.godpol engine: resolves namespace (including Namespace kind special case), calls Evaluate