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.
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 parameterlimitto be an integer >= 1.GET /cards/{id}: has nolimitconstraint.GET /cards/default?limit=0is correctly rejected with HTTP 400. However,GET /cards/%64efault?limit=0passes validation and reaches the literal/cards/defaulthandler, returning HTTP 200.This reproduces with the middleware wrapping a plain
http.ServeMux; no generated code or application framework is needed.Environment
go1.27.0 darwin/arm64github.com/oapi-codegen/nethttp-middleware v1.2.0github.com/getkin/kin-openapi v0.149.0github.com/gorilla/mux v1.8.1Minimal reproduction
Save the following as
main.goin a new directory: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:
Expected behavior
Validation should apply the constraints of the operation that
http.ServeMuxactually serves. Both requests above should be rejected with HTTP 400, since both target the literal operation andlimit=0violates its schema.Investigation
The middleware constructs a kin-openapi Gorilla router and independently calls
FindRoute. That router usesUseEncodedPath(), so/cards/%64efaultmatches the parameterized OpenAPI path/cards/{id}. Go'shttp.ServeMuxunescapes path segments for matching and selectsGET /cards/defaultinstead.I also confirmed this using
oapi-codegen v2.8.0withstd-http-serverandstrict-server, both with an outer validation middleware and throughStdHTTPServerOptions.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.