Skip to content

Case-insensitive usmarshalling for json/v1 and json-schema / openapi validation #1111

@aywan

Description

@aywan

Hi!

I found a tricky case that should probably be known about, but I couldn't find the information.

TLDR> validation is breaking due to problems in json/v1 with case insensitivity, which allows you to bypass validation.

It's easier to explain with an example:

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strings"

	"github.com/getkin/kin-openapi/openapi3"
	"github.com/getkin/kin-openapi/openapi3filter"
	"github.com/getkin/kin-openapi/routers/gorillamux"
)

var schema = `openapi: 3.0.0
info:
  title: Test API
  version: 1.0.0
paths:
  /test:
    post:
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                value:
                  type: string
                  enum:
                    - "aniki"
      responses:
        '204':
          description: OK
`

var examples = []struct {
	name string
	body string
}{
	{"good", `{"value": "aniki"}`},
	{"bad", `{"value": "beniki"}`},
	{"evil", `{"value": "aniki", "Value": "beniki"}`},
	{"chaotic evil", `{"Value": "beniki", "vaLue": "chaos"}`},
}

type ValueObj struct {
	Value string `json:"value"`
}

func main() {
	ctx := context.Background()
	loader := &openapi3.Loader{Context: ctx, IsExternalRefsAllowed: true}

	doc, err := loader.LoadFromData([]byte(schema))
	if err != nil {
		log.Fatal("load schema: ", err)
	}

	err = doc.Validate(ctx)
	if err != nil {
		log.Fatal("validate schema: ", err)
	}

	router, err := gorillamux.NewRouter(doc)
	if err != nil {
		log.Fatal("create router: ", err)
	}

	for _, ex := range examples {
		httpReq, _ := http.NewRequest(http.MethodPost, "/test", strings.NewReader(ex.body))
		httpReq.Header.Set("Content-Type", "application/json")

		route, pathParams, _ := router.FindRoute(httpReq)
		requestValidationInput := &openapi3filter.RequestValidationInput{
			Request:    httpReq,
			PathParams: pathParams,
			Route:      route,
		}

		err := openapi3filter.ValidateRequest(ctx, requestValidationInput)
		if err != nil {
			fmt.Println(ex.name, "failed:", err)
		} else {
			fmt.Println(ex.name, "passed")
		}

		var val ValueObj
		err = json.Unmarshal([]byte(ex.body), &val)
		if err != nil {
			log.Fatal("unmarshal: ", err)
		}
		fmt.Printf("%s value: %+v\n", ex.name, val)
		fmt.Println("-----")
	}
}

output:

good passed
good value: {Value:aniki}
-----
bad failed: request body has an error: doesn't match schema: Error at "/value": value is not one of the allowed values ["aniki"]
Schema:
  {
    "enum": [
      "aniki"
    ],
    "type": "string"
  }

Value:
  "beniki"

bad value: {Value:beniki}
-----
evil passed
evil value: {Value:beniki}
-----
chaotic evil passed
chaotic evil value: {Value:chaos}
-----

As you can see, you can slip in any value and bypass the validation. This means that in many cases it is not worth relying on validation.

https://github.com/getkin/kin-openapi/blob/master/openapi3/schema.go#L1999
It seems we need to fix the code here a bit to solve this problem. You need to add an option to reduce all names to the same case.

go.mod

go 1.25.5

require github.com/getkin/kin-openapi v0.133.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions