Skip to content

Validator and http.ServeMux select different operations for percent-encoded literal paths #89

Description

@emacampolo

Summary

With Go's http.ServeMux, the middleware can validate one OpenAPI operation while the server executes another when a literal path segment is percent-encoded.

Given these two paths:

  • GET /cards/default: requires the query parameter limit to be an integer >= 1.
  • GET /cards/{id}: has no limit constraint.

GET /cards/default?limit=0 is correctly rejected with HTTP 400. However, GET /cards/%64efault?limit=0 passes validation and reaches the literal /cards/default handler, returning HTTP 200.

This reproduces with the middleware wrapping a plain http.ServeMux; no generated code or application framework is needed.

Environment

  • Go: go1.27.0 darwin/arm64
  • github.com/oapi-codegen/nethttp-middleware v1.2.0
  • github.com/getkin/kin-openapi v0.149.0
  • github.com/gorilla/mux v1.8.1

Minimal reproduction

Save the following as main.go in a new directory:

package main

import (
	"fmt"
	"log"
	"net/http"
	"net/http/httptest"

	"github.com/getkin/kin-openapi/openapi3"
	middleware "github.com/oapi-codegen/nethttp-middleware"
)

const rawSpec = `openapi: 3.0.3
info:
  title: Routing mismatch reproduction
  version: 1.0.0
paths:
  /cards/default:
    get:
      operationId: getDefaultCard
      parameters:
        - name: limit
          in: query
          required: true
          schema:
            type: integer
            minimum: 1
      responses:
        '200':
          description: OK
  /cards/{id}:
    get:
      operationId: getCard
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: OK
`

func main() {
	specification, err := openapi3.NewLoader().LoadFromData([]byte(rawSpec))
	if err != nil {
		log.Fatal(err)
	}

	mux := http.NewServeMux()
	mux.HandleFunc("GET /cards/default", func(writer http.ResponseWriter, request *http.Request) {
		writer.Header().Set("X-Handler", "getDefaultCard")
		writer.WriteHeader(http.StatusOK)
	})
	mux.HandleFunc("GET /cards/{id}", func(writer http.ResponseWriter, request *http.Request) {
		writer.Header().Set("X-Handler", "getCard")
		writer.WriteHeader(http.StatusOK)
	})
	handler := middleware.OapiRequestValidator(specification)(mux)

	for _, path := range []string{"/cards/default?limit=0", "/cards/%64efault?limit=0"} {
		request := httptest.NewRequest(http.MethodGet, path, nil)
		response := httptest.NewRecorder()
		handler.ServeHTTP(response, request)
		fmt.Printf("%s -> status=%d handler=%q pattern=%q\n",
			path, response.Code, response.Header().Get("X-Handler"), request.Pattern)
	}
}

Run:

go mod init example.com/servemux-validation-repro
go get github.com/oapi-codegen/nethttp-middleware@v1.2.0 github.com/getkin/kin-openapi@v0.149.0
go run .

Actual output:

/cards/default?limit=0 -> status=400 handler="" pattern=""
/cards/%64efault?limit=0 -> status=200 handler="getDefaultCard" pattern="GET /cards/default"

Expected behavior

Validation should apply the constraints of the operation that http.ServeMux actually serves. Both requests above should be rejected with HTTP 400, since both target the literal operation and limit=0 violates its schema.

Investigation

The middleware constructs a kin-openapi Gorilla router and independently calls FindRoute. That router uses UseEncodedPath(), so /cards/%64efault matches the parameterized OpenAPI path /cards/{id}. Go's http.ServeMux unescapes path segments for matching and selects GET /cards/default instead.

I also confirmed this using oapi-codegen v2.8.0 with std-http-server and strict-server, both with an outer validation middleware and through StdHTTPServerOptions.Middlewares. Moving the middleware does not resolve the mismatch.

Relevant implementation:

Related issue and question

This is related to #41, but differs from validating an encoded parameter value against the wrong length or pattern: here, the middleware and the server select different operations, so the executed operation's query constraints are not checked.

Is there a supported way to keep validation aligned with http.ServeMux's operation selection without application-level URL rewriting or custom routing adapters? If this is a bug, should the fix live here or in kin-openapi?

I am not assuming that replacing Gorilla is the required solution; the desired outcome is consistent operation selection between validation and request handling.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions