VYPR
High severityNVD Advisory· Published Jul 23, 2025· Updated Jul 23, 2025

Kyverno's Improper JMESPath Variable Evaluation Leads to Denial of Service

CVE-2025-47281

Description

Kyverno is a policy engine designed for cloud native platform engineering teams. In versions 1.14.1 and below, a Denial of Service (DoS) vulnerability exists due to improper handling of JMESPath variable substitutions. Attackers with permissions to create or update Kyverno policies can craft expressions using the {{@}} variable combined with a pipe and an invalid JMESPath function (e.g., {{@ | non_existent_function }}). This leads to a nil value being substituted into the policy structure. Subsequent processing by internal functions, specifically getValueAsStringMap, which expect string values, results in a panic due to a type assertion failure (interface {} is nil, not string). This crashes Kyverno worker threads in the admission controller and causes continuous crashes of the reports controller pod. This is fixed in version 1.14.2.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/kyverno/kyvernoGo
< 1.14.21.14.2

Affected products

1

Patches

1
cbd7d4ca24de

Merge commit from fork (#13138)

https://github.com/kyverno/kyvernoshutingMay 15, 2025via ghsa
2 files changed · +250 1
  • pkg/engine/wildcards/wildcards.go+10 1 modified
    @@ -135,7 +135,16 @@ func getValueAsStringMap(key string, data interface{}) (string, map[string]strin
     	}
     
     	for k, v := range valMap {
    -		result[k] = v.(string)
    +		if v == nil {
    +			continue
    +		}
    +
    +		switch typedVal := v.(type) {
    +		case string:
    +			result[k] = typedVal
    +		default:
    +			continue
    +		}
     	}
     
     	return patternKey, result
    
  • pkg/engine/wildcards/wildcards_test.go+240 0 modified
    @@ -3,6 +3,8 @@ package wildcards
     import (
     	"reflect"
     	"testing"
    +
    +	"github.com/stretchr/testify/assert"
     )
     
     func TestExpandInMetadata(t *testing.T) {
    @@ -25,3 +27,241 @@ func testExpand(t *testing.T, patternMap, resourceMap map[string]string, expecte
     		t.Errorf("expected %v but received %v", expectedMap, result)
     	}
     }
    +
    +func TestGetValueAsStringMap_NilHandling(t *testing.T) {
    +	tests := []struct {
    +		name           string
    +		key            string
    +		data           interface{}
    +		expectedKey    string
    +		expectedResult map[string]string
    +	}{
    +		{
    +			name:           "nil data",
    +			key:            "test",
    +			data:           nil,
    +			expectedKey:    "",
    +			expectedResult: nil,
    +		},
    +		{
    +			name:           "data is not a map",
    +			key:            "test",
    +			data:           "not a map",
    +			expectedKey:    "",
    +			expectedResult: nil,
    +		},
    +		{
    +			name:           "key not found",
    +			key:            "nonexistent",
    +			data:           map[string]interface{}{"otherKey": "value"},
    +			expectedKey:    "",
    +			expectedResult: nil,
    +		},
    +		{
    +			name:           "value is nil",
    +			key:            "test",
    +			data:           map[string]interface{}{"test": nil},
    +			expectedKey:    "",
    +			expectedResult: nil,
    +		},
    +		{
    +			name:           "value is not a map",
    +			key:            "test",
    +			data:           map[string]interface{}{"test": "not a map"},
    +			expectedKey:    "",
    +			expectedResult: nil,
    +		},
    +		{
    +			name: "handles nil value in map",
    +			key:  "test",
    +			data: map[string]interface{}{
    +				"test": map[string]interface{}{
    +					"key1": "value1",
    +					"key2": nil,
    +					"key3": "value3",
    +				},
    +			},
    +			expectedKey: "test",
    +			expectedResult: map[string]string{
    +				"key1": "value1",
    +				// key2 should be skipped
    +				"key3": "value3",
    +			},
    +		},
    +		{
    +			name: "handles non-string value in map",
    +			key:  "test",
    +			data: map[string]interface{}{
    +				"test": map[string]interface{}{
    +					"key1": "value1",
    +					"key2": 123,
    +					"key3": map[string]string{
    +						"nested": "value",
    +					},
    +					"key4": "value4",
    +				},
    +			},
    +			expectedKey: "test",
    +			expectedResult: map[string]string{
    +				"key1": "value1",
    +				// key2 should be skipped (non-string)
    +				// key3 should be skipped (complex)
    +				"key4": "value4",
    +			},
    +		},
    +		{
    +			name: "normal case - all strings",
    +			key:  "test",
    +			data: map[string]interface{}{
    +				"test": map[string]interface{}{
    +					"key1": "value1",
    +					"key2": "value2",
    +				},
    +			},
    +			expectedKey: "test",
    +			expectedResult: map[string]string{
    +				"key1": "value1",
    +				"key2": "value2",
    +			},
    +		},
    +	}
    +
    +	for _, test := range tests {
    +		t.Run(test.name, func(t *testing.T) {
    +			key, result := getValueAsStringMap(test.key, test.data)
    +			assert.Equal(t, test.expectedKey, key)
    +			assert.Equal(t, test.expectedResult, result)
    +		})
    +	}
    +}
    +
    +func TestExpandInMetadata_NilSafety(t *testing.T) {
    +	testCases := []struct {
    +		name        string
    +		patternMap  map[string]interface{}
    +		resourceMap map[string]interface{}
    +		shouldPanic bool
    +	}{
    +		{
    +			name: "nil value in annotation should not panic",
    +			patternMap: map[string]interface{}{
    +				"metadata": map[string]interface{}{
    +					"annotations": map[string]interface{}{
    +						"some-key": nil,
    +					},
    +				},
    +			},
    +			resourceMap: map[string]interface{}{
    +				"metadata": map[string]interface{}{
    +					"annotations": map[string]interface{}{
    +						"real-key": "real-value",
    +					},
    +				},
    +			},
    +			shouldPanic: false,
    +		},
    +		{
    +			name: "complex value in annotation should not panic",
    +			patternMap: map[string]interface{}{
    +				"metadata": map[string]interface{}{
    +					"annotations": map[string]interface{}{
    +						"some-key": map[string]interface{}{
    +							"nested": "value",
    +						},
    +					},
    +				},
    +			},
    +			resourceMap: map[string]interface{}{
    +				"metadata": map[string]interface{}{
    +					"annotations": map[string]interface{}{
    +						"real-key": "real-value",
    +					},
    +				},
    +			},
    +			shouldPanic: false,
    +		},
    +		{
    +			name: "simulated jmespath nil result",
    +			patternMap: map[string]interface{}{
    +				"metadata": map[string]interface{}{
    +					"annotations": map[string]interface{}{
    +						"test": nil, // Simulating what happens when {{@ | foo}} evaluates with undefined 'foo'
    +					},
    +				},
    +			},
    +			resourceMap: map[string]interface{}{
    +				"metadata": map[string]interface{}{
    +					"annotations": map[string]interface{}{
    +						"real-key": "real-value",
    +					},
    +				},
    +			},
    +			shouldPanic: false,
    +		},
    +	}
    +
    +	for _, tc := range testCases {
    +		t.Run(tc.name, func(t *testing.T) {
    +			defer func() {
    +				r := recover()
    +				if tc.shouldPanic {
    +					assert.NotNil(t, r, "Expected function to panic, but it didn't")
    +				} else {
    +					assert.Nil(t, r, "Function panicked unexpectedly")
    +				}
    +			}()
    +
    +			ExpandInMetadata(tc.patternMap, tc.resourceMap)
    +		})
    +	}
    +}
    +
    +func TestJMESPathNil(t *testing.T) {
    +	// Create a pattern with a nil value in labels or annotations
    +	// to simulate what happens after a JMESPath expression like {{@ | foo}}
    +	// (where 'foo' is not a defined function) is evaluated and results in nil.
    +	patternMap := map[string]interface{}{
    +		"metadata": map[string]interface{}{
    +			"labels": map[string]interface{}{
    +				"normal":    "value",
    +				"nil-value": nil, // This represents the result of {{@ | foo}} substitution
    +			},
    +			"annotations": map[string]interface{}{
    +				"another-normal": "value",
    +				"complex-value": map[string]interface{}{ // And this represents a complex structure
    +					"nested": "value",
    +				},
    +			},
    +		},
    +	}
    +
    +	resourceMap := map[string]interface{}{
    +		"metadata": map[string]interface{}{
    +			"labels": map[string]interface{}{
    +				"app": "test",
    +			},
    +			"annotations": map[string]interface{}{
    +				"test": "value",
    +			},
    +		},
    +	}
    +
    +	defer func() {
    +		r := recover()
    +		assert.Nil(t, r, "ExpandInMetadata should not panic with nil values, but it did: %v", r)
    +	}()
    +
    +	result := ExpandInMetadata(patternMap, resourceMap)
    +
    +	// Additional verification that the function works correctly
    +	metadataResult := result["metadata"].(map[string]interface{})
    +	labelsResult, ok := metadataResult["labels"].(map[string]interface{})
    +
    +	assert.True(t, ok, "Expected labels to be a map[string]interface{}")
    +	assert.Contains(t, labelsResult, "normal")
    +
    +	// Annotations with complex values should also be handled properly
    +	annotationsResult, ok := metadataResult["annotations"].(map[string]interface{})
    +	assert.True(t, ok, "Expected annotations to be a map[string]interface{}")
    +	assert.Contains(t, annotationsResult, "another-normal")
    +}
    

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

4

News mentions

0

No linked articles in our index yet.