From d2d6c8e9ed0ac298e71c343d98b5570da2c00048 Mon Sep 17 00:00:00 2001 From: Sebastien Tardif Date: Fri, 29 May 2026 09:20:30 -0700 Subject: [PATCH 1/2] Fix panic on missing or non-map query parameter object Replace bare type assertion with comma-ok pattern to prevent panic when query parameter object key is absent or value is not a map. --- parameters/query_parameters.go | 14 +++++++++- parameters/query_parameters_test.go | 42 +++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/parameters/query_parameters.go b/parameters/query_parameters.go index 5b24d6d..bbf40ef 100644 --- a/parameters/query_parameters.go +++ b/parameters/query_parameters.go @@ -197,9 +197,21 @@ doneLooking: } } + if encodedObj == nil { + break skipValues + } + objVal, objExists := encodedObj[params[p].Name] + if !objExists || objVal == nil { + break skipValues + } + objMap, mapOk := objVal.(map[string]interface{}) + if !mapOk { + break skipValues + } + numErrors := len(validationErrors) validationErrors = append(validationErrors, - ValidateParameterSchema(sch, encodedObj[params[p].Name].(map[string]interface{}), + ValidateParameterSchema(sch, objMap, ef, "Query parameter", "The query parameter", diff --git a/parameters/query_parameters_test.go b/parameters/query_parameters_test.go index 1bc9ca7..9c6b209 100644 --- a/parameters/query_parameters_test.go +++ b/parameters/query_parameters_test.go @@ -4140,3 +4140,45 @@ paths: assert.True(t, valid, "issue #91: item_count=10 with type: string should not fail with 'expected string, but got number'") assert.Empty(t, errors) } + +func TestQueryParamObjectMissingKey_NoPanic(t *testing.T) { + // This test verifies that the validator does not panic when a query parameter + // is declared as type: object with a content media type that is not JSON. + // Previously a bare type assertion on encodedObj[paramName] would panic when + // encodedObj was nil (non-JSON content wrapper) or the key was absent. + spec := `openapi: 3.1.0 +paths: + /test: + get: + parameters: + - name: filter + in: query + required: false + content: + text/plain: + schema: + type: object + properties: + color: + type: string + operationId: testFilter +` + + doc, err := libopenapi.NewDocument([]byte(spec)) + require.NoError(t, err) + + m, errs := doc.BuildV3Model() + require.NoError(t, errs) + + v := NewParameterValidator(&m.Model) + + // Send a request with the filter parameter present; because the content type + // is text/plain (not application/json), the Object branch cannot decode the + // value into a map and previously panicked. + request, _ := http.NewRequest(http.MethodGet, "https://example.com/test?filter=color,blue", nil) + + // Must not panic. + assert.NotPanics(t, func() { + v.ValidateQueryParams(request) + }) +} From d002a64859e49d8fd3beae843c78853f90cb5a83 Mon Sep 17 00:00:00 2001 From: Sebastien Tardif Date: Tue, 2 Jun 2026 06:10:08 -0700 Subject: [PATCH 2/2] fix: return validation error when object query param cannot be decoded The nil/type guards prevented the panic but also silently accepted requests where a content-wrapped object parameter could not be decoded into a map. Now each guard emits a QueryParameterCannotBeDecoded validation error before breaking, so the request correctly fails validation instead of passing silently. Add QueryParameterCannotBeDecoded error constructor (modeled after HeaderParameterCannotBeDecoded) and update the test to assert on the returned error. Add a positive test for valid JSON object parameters. Signed-off-by: Sebastien Tardif --- errors/parameter_errors.go | 25 +++++++++++++ parameters/query_parameters.go | 6 +++ parameters/query_parameters_test.go | 57 ++++++++++++++++++++++++----- 3 files changed, 79 insertions(+), 9 deletions(-) diff --git a/errors/parameter_errors.go b/errors/parameter_errors.go index 48ee25a..aa870c9 100644 --- a/errors/parameter_errors.go +++ b/errors/parameter_errors.go @@ -470,6 +470,31 @@ func IncorrectCookieParamArrayNumber( } } +func QueryParameterCannotBeDecoded(param *v3.Parameter, val string, sch *base.Schema, pathTemplate string, operation string, renderedSchema string) *ValidationError { + keywordLocation := helpers.ConstructParameterJSONPointer(pathTemplate, operation, param.Name, "type") + specLine, specCol := paramSchemaTypeLineCol(param) + + return &ValidationError{ + ValidationType: helpers.ParameterValidation, + ValidationSubType: helpers.ParameterValidationQuery, + Message: fmt.Sprintf("Query parameter '%s' cannot be decoded", param.Name), + Reason: fmt.Sprintf("The query parameter '%s' is defined as an object, "+ + "however the value '%s' cannot be decoded as an object", param.Name, val), + SpecLine: specLine, + SpecCol: specCol, + ParameterName: param.Name, + Context: sch, + HowToFix: HowToFixInvalidEncoding, + SchemaValidationErrors: []*SchemaValidationFailure{{ + Reason: fmt.Sprintf("Query value '%s' cannot be decoded as object", val), + FieldName: param.Name, + InstancePath: []string{param.Name}, + KeywordLocation: keywordLocation, + ReferenceSchema: renderedSchema, + }}, + } +} + func IncorrectParamEncodingJSON(param *v3.Parameter, ef string, sch *base.Schema, pathTemplate string, operation string, renderedSchema string) *ValidationError { escapedPath := strings.ReplaceAll(pathTemplate, "~", "~0") escapedPath = strings.ReplaceAll(escapedPath, "/", "~1") diff --git a/parameters/query_parameters.go b/parameters/query_parameters.go index bbf40ef..b775ffb 100644 --- a/parameters/query_parameters.go +++ b/parameters/query_parameters.go @@ -198,14 +198,20 @@ doneLooking: } if encodedObj == nil { + validationErrors = append(validationErrors, + errors.QueryParameterCannotBeDecoded(params[p], ef, sch, pathValue, operation, renderedSchema)) break skipValues } objVal, objExists := encodedObj[params[p].Name] if !objExists || objVal == nil { + validationErrors = append(validationErrors, + errors.QueryParameterCannotBeDecoded(params[p], ef, sch, pathValue, operation, renderedSchema)) break skipValues } objMap, mapOk := objVal.(map[string]interface{}) if !mapOk { + validationErrors = append(validationErrors, + errors.QueryParameterCannotBeDecoded(params[p], ef, sch, pathValue, operation, renderedSchema)) break skipValues } diff --git a/parameters/query_parameters_test.go b/parameters/query_parameters_test.go index 9c6b209..f6289e2 100644 --- a/parameters/query_parameters_test.go +++ b/parameters/query_parameters_test.go @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/require" "github.com/pb33f/libopenapi-validator/config" + liberrors "github.com/pb33f/libopenapi-validator/errors" "github.com/pb33f/libopenapi-validator/paths" ) @@ -4142,10 +4143,9 @@ paths: } func TestQueryParamObjectMissingKey_NoPanic(t *testing.T) { - // This test verifies that the validator does not panic when a query parameter - // is declared as type: object with a content media type that is not JSON. - // Previously a bare type assertion on encodedObj[paramName] would panic when - // encodedObj was nil (non-JSON content wrapper) or the key was absent. + // A content-wrapped object parameter with a non-JSON content type cannot + // be decoded into a map. The validator must not panic AND must report a + // validation error (the parameter is present but unsatisfiable). spec := `openapi: 3.1.0 paths: /test: @@ -4172,13 +4172,52 @@ paths: v := NewParameterValidator(&m.Model) - // Send a request with the filter parameter present; because the content type - // is text/plain (not application/json), the Object branch cannot decode the - // value into a map and previously panicked. request, _ := http.NewRequest(http.MethodGet, "https://example.com/test?filter=color,blue", nil) - // Must not panic. + var valid bool + var valErrors []*liberrors.ValidationError assert.NotPanics(t, func() { - v.ValidateQueryParams(request) + valid, valErrors = v.ValidateQueryParams(request) }) + + assert.False(t, valid, "parameter present but not decodable as object should be invalid") + require.Len(t, valErrors, 1) + assert.Equal(t, "Query parameter 'filter' cannot be decoded", valErrors[0].Message) +} + +func TestQueryParamObjectContentJSON_ValidObject(t *testing.T) { + // A content-wrapped JSON parameter with a valid JSON object should validate + // successfully against the schema. + spec := `openapi: 3.1.0 +paths: + /test: + get: + parameters: + - name: filter + in: query + required: false + content: + application/json: + schema: + type: object + properties: + color: + type: string + operationId: testFilter +` + + doc, err := libopenapi.NewDocument([]byte(spec)) + require.NoError(t, err) + + m, errs := doc.BuildV3Model() + require.NoError(t, errs) + + v := NewParameterValidator(&m.Model) + + request, _ := http.NewRequest(http.MethodGet, `https://example.com/test?filter={"color":"blue"}`, nil) + + valid, valErrors := v.ValidateQueryParams(request) + + assert.True(t, valid, "valid JSON object should pass validation") + assert.Empty(t, valErrors) }