Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 80 additions & 97 deletions parameters/validate_parameter.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,48 +37,11 @@ func ValidateSingleParameterSchema(
pathTemplate string,
operation string,
) (validationErrors []*errors.ValidationError) {
var jsch *jsonschema.Schema
var referenceSchema string

// Try cache lookup first - avoids expensive schema compilation on each request
if o != nil && o.SchemaCache != nil && schema != nil && schema.GoLow() != nil {
hash := schema_validation.SchemaCacheKey(
schema.GoLow().Hash(),
parameterSchemaVersion,
schema_validation.SchemaValidationPurposeGeneric,
)
if cached, ok := o.SchemaCache.Load(hash); ok && cached != nil && cached.CompiledSchema != nil {
jsch = cached.CompiledSchema
referenceSchema = cached.ReferenceSchema
}
}

// Cache miss - compile the schema
if jsch == nil {
compiled, err := schema_validation.CompileSchemaForValidation(
schema,
schema_validation.SchemaValidationPurposeGeneric,
o,
parameterSchemaVersion,
)
if err != nil {
return validationErrors
}
if compiled == nil || compiled.CompiledSchema == nil {
return validationErrors
}
jsch = compiled.CompiledSchema
referenceSchema = compiled.ReferenceSchema

// Store in cache for future requests
if o != nil && o.SchemaCache != nil && schema != nil && schema.GoLow() != nil {
hash := schema_validation.SchemaCacheKey(
schema.GoLow().Hash(),
parameterSchemaVersion,
schema_validation.SchemaValidationPurposeGeneric,
)
o.SchemaCache.Store(hash, compiled.ToCacheEntry(schema))
}
jsch, referenceSchema, validationErrors := compileParameterValidationSchema(
schema, o, entity, reasonEntity, name, validationType, subValType,
)
if jsch == nil || len(validationErrors) > 0 {
return validationErrors
}

// Validate the object and report any errors.
Expand Down Expand Up @@ -138,61 +101,11 @@ func ValidateParameterSchema(
validationOptions *config.ValidationOptions,
) []*errors.ValidationError {
var validationErrors []*errors.ValidationError
var jsch *jsonschema.Schema
var referenceSchema string

// Try cache lookup first - avoids expensive schema compilation on each request
if validationOptions != nil && validationOptions.SchemaCache != nil && schema != nil && schema.GoLow() != nil {
hash := schema_validation.SchemaCacheKey(
schema.GoLow().Hash(),
parameterSchemaVersion,
schema_validation.SchemaValidationPurposeGeneric,
)
if cached, ok := validationOptions.SchemaCache.Load(hash); ok && cached != nil && cached.CompiledSchema != nil {
jsch = cached.CompiledSchema
referenceSchema = cached.ReferenceSchema
}
}

// Cache miss - render and compile the schema
if jsch == nil {
compiled, err := schema_validation.CompileSchemaForValidation(
schema,
schema_validation.SchemaValidationPurposeGeneric,
validationOptions,
parameterSchemaVersion,
)
if err != nil {
// schema compilation failed, return validation error instead of panicking
validationErrors = append(validationErrors, &errors.ValidationError{
ValidationType: validationType,
ValidationSubType: subValType,
Message: fmt.Sprintf("%s '%s' failed schema compilation", entity, name),
Reason: fmt.Sprintf("%s '%s' schema compilation failed: %s",
reasonEntity, name, err.Error()),
SpecLine: 1,
SpecCol: 0,
ParameterName: name,
HowToFix: "check the parameter schema for invalid JSON Schema syntax, complex regex patterns, or unsupported schema constructs",
Context: schema,
})
return validationErrors
}
if compiled == nil || compiled.CompiledSchema == nil {
return validationErrors
}
jsch = compiled.CompiledSchema
referenceSchema = compiled.ReferenceSchema

// Store in cache for future requests
if validationOptions != nil && validationOptions.SchemaCache != nil && schema != nil && schema.GoLow() != nil {
hash := schema_validation.SchemaCacheKey(
schema.GoLow().Hash(),
parameterSchemaVersion,
schema_validation.SchemaValidationPurposeGeneric,
)
validationOptions.SchemaCache.Store(hash, compiled.ToCacheEntry(schema))
}
jsch, referenceSchema, validationErrors := compileParameterValidationSchema(
schema, validationOptions, entity, reasonEntity, name, validationType, subValType,
)
if jsch == nil || len(validationErrors) > 0 {
return validationErrors
}

// 3. decode the object into a json blob.
Expand Down Expand Up @@ -289,6 +202,76 @@ func ValidateParameterSchema(
return validationErrors
}

func compileParameterValidationSchema(
schema *base.Schema,
validationOptions *config.ValidationOptions,
entity string,
reasonEntity string,
name string,
validationType string,
subValType string,
) (*jsonschema.Schema, string, []*errors.ValidationError) {
if validationOptions != nil && validationOptions.SchemaCache != nil && schema != nil && schema.GoLow() != nil {
hash := schema_validation.SchemaCacheKey(
schema.GoLow().Hash(),
parameterSchemaVersion,
schema_validation.SchemaValidationPurposeGeneric,
)
if cached, ok := validationOptions.SchemaCache.Load(hash); ok && cached != nil && cached.CompiledSchema != nil {
return cached.CompiledSchema, cached.ReferenceSchema, nil
}
}

compiled, err := schema_validation.CompileSchemaForValidation(
schema,
schema_validation.SchemaValidationPurposeGeneric,
validationOptions,
parameterSchemaVersion,
)
if err != nil {
return nil, "", []*errors.ValidationError{
parameterSchemaCompilationError(schema, err, entity, reasonEntity, name, validationType, subValType),
}
}
if compiled == nil || compiled.CompiledSchema == nil {
return nil, "", nil
}

if validationOptions != nil && validationOptions.SchemaCache != nil && schema != nil && schema.GoLow() != nil {
hash := schema_validation.SchemaCacheKey(
schema.GoLow().Hash(),
parameterSchemaVersion,
schema_validation.SchemaValidationPurposeGeneric,
)
validationOptions.SchemaCache.Store(hash, compiled.ToCacheEntry(schema))
}

return compiled.CompiledSchema, compiled.ReferenceSchema, nil
}

func parameterSchemaCompilationError(
schema *base.Schema,
err error,
entity string,
reasonEntity string,
name string,
validationType string,
subValType string,
) *errors.ValidationError {
return &errors.ValidationError{
ValidationType: validationType,
ValidationSubType: subValType,
Message: fmt.Sprintf("%s '%s' failed schema compilation", entity, name),
Reason: fmt.Sprintf("%s '%s' schema compilation failed: %s",
reasonEntity, name, err.Error()),
SpecLine: 1,
SpecCol: 0,
ParameterName: name,
HowToFix: "check the parameter schema for invalid JSON Schema syntax, complex regex patterns, or unsupported schema constructs",
Context: schema,
}
}

func formatJsonSchemaValidationError(
schema *base.Schema,
scErrs *jsonschema.ValidationError,
Expand Down
46 changes: 46 additions & 0 deletions parameters/validate_parameter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,52 @@ func TestComplexRegexSchemaCompilationError(t *testing.T) {
}
}

func TestValidateQueryParams_LookaheadPatternCompilationFailureFailsClosed(t *testing.T) {
spec := []byte(`openapi: 3.1.0
info:
title: Lookahead Repro
version: 1.0.0
paths:
/items:
get:
parameters:
- name: code
in: query
required: true
schema:
type: string
pattern: '^(?!bad).+$'
responses:
"200":
description: ok
`)

doc, err := libopenapi.NewDocument(spec)
require.NoError(t, err)

v3Model, errs := doc.BuildV3Model()
require.NoError(t, errs)

validator := NewParameterValidator(&v3Model.Model)

for _, value := range []string{"badcat", "goodcat"} {
req, err := http.NewRequest(http.MethodGet, "/items?code="+value, nil)
require.NoError(t, err)

valid, validationErrors := validator.ValidateQueryParams(req)

require.False(t, valid, "schema compilation failures must not pass validation for %q", value)
require.NotEmpty(t, validationErrors)
assert.Equal(t, "code", validationErrors[0].ParameterName)
assert.Equal(t, helpers.ParameterValidation, validationErrors[0].ValidationType)
assert.Equal(t, helpers.ParameterValidationQuery, validationErrors[0].ValidationSubType)
assert.Contains(t, validationErrors[0].Message, "failed schema compilation")
assert.Contains(t, validationErrors[0].Reason, "schema compilation failed")
assert.Contains(t, validationErrors[0].HowToFix, "complex regex patterns")
assert.Empty(t, validationErrors[0].SchemaValidationErrors)
}
}

// TestValidateParameterSchema_SchemaCompilationFailure tests that ValidateParameterSchema
// handles schema compilation failures gracefully instead of causing panics
func TestValidateParameterSchema_SchemaCompilationFailure(t *testing.T) {
Expand Down
Loading