CVE-2026-41485
Description
Kyverno is a policy engine designed for cloud native platform engineering teams. Prior to versions 1.17.2 and 1.16.4, an unchecked type assertion in the forEach mutation handler allows any user with permission to create a Policy or ClusterPolicy to crash the cluster-wide background controller into a persistent CrashLoopBackOff. The same bug also causes the admission controller to drop connections and block all matching resource operations. The crash loop persists until the policy is deleted. The vulnerability is confined to the legacy engine, and CEL-based policies are unaffected. Versions 1.17.2 and 1.16.4 fix the issue.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/kyverno/kyvernoGo | >= 1.13.0, < 1.16.4 | 1.16.4 |
github.com/kyverno/kyvernoGo | >= 1.17.0-rc.1, < 1.17.2 | 1.17.2 |
Affected products
2Patches
280e728c2283aMerge commit from fork (#15887)
2 files changed · +37 −1
pkg/engine/mutate/mutation.go+2 −1 modified@@ -78,7 +78,8 @@ func ForEach(name string, foreach kyvernov1.ForEachMutation, policyContext engin if err != nil { return NewErrorResponse("variable substitution failed", err) } - patcher := NewPatcher(fe["patchStrategicMerge"], fe["patchesJson6902"].(string)) + jsonPatch, _ := fe["patchesJson6902"].(string) + patcher := NewPatcher(fe["patchStrategicMerge"], jsonPatch) if patcher == nil { return NewErrorResponse("empty mutate rule", nil) }
pkg/engine/mutate/mutation_test.go+35 −0 modified@@ -238,6 +238,16 @@ func TestProcessPatches_RemovePathDoesntExist_NotEmptyResult(t *testing.T) { require.Equal(t, resource, patched) } +type MockPolicyContext struct { + engineapi.PolicyContext + mock.Mock +} + +func (m *MockPolicyContext) JSONContext() context.Interface { + args := m.Called() + return args.Get(0).(context.Interface) +} + type MockContext struct { context.Interface mock.Mock @@ -253,6 +263,31 @@ func (m *MockContext) QueryOperation() string { return args.Get(0).(string) } +func TestForEach_NilPatchesJSON6902_NoPanic(t *testing.T) { + ctx := &MockContext{} + // Variable resolves to nil, which caused a bare type assertion panic before the fix + ctx.On("Query", mock.Anything).Return(nil, nil) + ctx.On("QueryOperation").Return("CREATE") + + foreach := v1.ForEachMutation{ + PatchesJSON6902: "{{ element.nonexistent }}", + } + + var resource unstructured.Unstructured + resource.SetUnstructuredContent(map[string]interface{}{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": map[string]interface{}{"name": "test"}, + }) + + policyContext := &MockPolicyContext{} + policyContext.On("JSONContext").Return(ctx) + + resp := ForEach("test-rule", foreach, policyContext, resource, nil, logr.Discard()) + assert.NotNil(t, resp) + assert.Equal(t, engineapi.RuleStatusError, resp.Status) +} + func TestSubstituteAllInForEach_InvalidTypeConversion(t *testing.T) { ctx := &MockContext{} // Simulate a scenario where the substitution returns an unexpected type
76c8fdbe8732Merge commit from fork (#15888)
2 files changed · +37 −1
pkg/engine/mutate/mutation.go+2 −1 modified@@ -78,7 +78,8 @@ func ForEach(name string, foreach kyvernov1.ForEachMutation, policyContext engin if err != nil { return NewErrorResponse("variable substitution failed", err) } - patcher := NewPatcher(fe["patchStrategicMerge"], fe["patchesJson6902"].(string)) + jsonPatch, _ := fe["patchesJson6902"].(string) + patcher := NewPatcher(fe["patchStrategicMerge"], jsonPatch) if patcher == nil { return NewErrorResponse("empty mutate rule", nil) }
pkg/engine/mutate/mutation_test.go+35 −0 modified@@ -238,6 +238,16 @@ func TestProcessPatches_RemovePathDoesntExist_NotEmptyResult(t *testing.T) { require.Equal(t, resource, patched) } +type MockPolicyContext struct { + engineapi.PolicyContext + mock.Mock +} + +func (m *MockPolicyContext) JSONContext() context.Interface { + args := m.Called() + return args.Get(0).(context.Interface) +} + type MockContext struct { context.Interface mock.Mock @@ -253,6 +263,31 @@ func (m *MockContext) QueryOperation() string { return args.Get(0).(string) } +func TestForEach_NilPatchesJSON6902_NoPanic(t *testing.T) { + ctx := &MockContext{} + // Variable resolves to nil, which caused a bare type assertion panic before the fix + ctx.On("Query", mock.Anything).Return(nil, nil) + ctx.On("QueryOperation").Return("CREATE") + + foreach := v1.ForEachMutation{ + PatchesJSON6902: "{{ element.nonexistent }}", + } + + var resource unstructured.Unstructured + resource.SetUnstructuredContent(map[string]interface{}{ + "apiVersion": "v1", + "kind": "ConfigMap", + "metadata": map[string]interface{}{"name": "test"}, + }) + + policyContext := &MockPolicyContext{} + policyContext.On("JSONContext").Return(ctx) + + resp := ForEach("test-rule", foreach, policyContext, resource, nil, logr.Discard()) + assert.NotNil(t, resp) + assert.Equal(t, engineapi.RuleStatusError, resp.Status) +} + func TestSubstituteAllInForEach_InvalidTypeConversion(t *testing.T) { ctx := &MockContext{} // Simulate a scenario where the substitution returns an unexpected type
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/kyverno/kyverno/commit/76c8fdbe87328722e099e1fd44c3f21c9f7809cbnvdPatchWEB
- github.com/kyverno/kyverno/commit/80e728c2283a0c65e5adb02d8a907106e6ebe7e3nvdPatchWEB
- github.com/kyverno/kyverno/security/advisories/GHSA-fpjq-c37h-cqcvnvdExploitVendor AdvisoryWEB
- github.com/advisories/GHSA-fpjq-c37h-cqcvghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-41485ghsaADVISORY
News mentions
0No linked articles in our index yet.