Skip to content

chore(deps): update module github.com/getkin/kin-openapi to v0.149.0 [security] - #569

Merged
renovate[bot] merged 1 commit into
mainfrom
renovate/go-github.com-getkin-kin-openapi-vulnerability
Oct 16, 2026
Merged

renovate[bot] merged 1 commit into
mainfrom
renovate/go-github.com-getkin-kin-openapi-vulnerability

Conversation

@renovate

@renovate renovate Bot commented Oct 15, 2026

Copy link
Copy Markdown
Contributor

This PR contains the following updates:

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

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 legacy router (routers/legacy). That router matches the incoming request method verbatim against a trie built exclusively from upper-cased canonical HTTP methods. A request carrying a method token that is not one of the 9 standard methods — a legitimate-but-unsupported token such as PROPFIND, or a non-canonical case variant such as lowercase get — misses the trie, leaves route == nil, and for an existing static path falls through to PathItem.GetOperation(method), whose default: case calls panic(...). The panic fires inside route resolution, before any authentication runs. This is an unauthenticated, remotely triggerable denial of service (recovered per-connection panic + unbounded error-log growth).

Root Cause
  • openapi3filter/validation_handler.go:39 — ValidationHandler.Load() always constructs legacyrouter.NewRouter(doc); there is no alternative router option on this path.
  • routers/legacy/router.go:109 — the request method is used verbatim (method, url := req.Method, ...); there is no canonicalization or rejection of unknown method tokens.
  • routers/legacy/router.go:69-70 — the trie is populated with strings.ToUpper(method) keys taken from pathItem.Operations() (canonical http.Method* constants only).
  • routers/legacy/router.go:141 — root.Match(method + " " + remainingPath) misses for a non-canonical/unsupported method → route == nil.
  • routers/legacy/router.go:146-150 — in the route == nil block, doc.Paths.Value(path) returns non-nil for an existing static path, and control reaches pathItem.GetOperation(method).
  • openapi3/path_item.go:174 — GetOperation's switch method has no matching case → default: panic(fmt.Errorf("unsupported HTTP method %q", method)).
Impact

An unauthenticated attacker sends a single request with a non-standard HTTP method token to any existing static path. The handler goroutine panics; Go's net/http recovers the panic per connection (process survives), aborts the connection (client sees a reset / 500), and logs a full stack trace. Repeated requests cause connection churn and unbounded http: panic serving log growth, and the request never reaches authentication. Availability impact (A:L) under the default synchronous ValidationHandler middleware; on non-net/http hosts or when reached on an application goroutine the ceiling is a full crash.

Proof of Concept

Serve any spec with a static path (e.g. /ping) behind openapi3filter.ValidationHandler, then:

printf 'PROPFIND /ping HTTP/1.1\r\nHost: x\r\n\r\n' | nc target 8080   # connection aborted (panic)
printf 'get /ping HTTP/1.1\r\nHost: x\r\n\r\n'      | nc target 8080   # connection aborted (panic)
printf 'GET /ping HTTP/1.1\r\nHost: x\r\n\r\n'      | nc target 8080   # 200 OK (process survived)

Reproduced end-to-end against a real net/http.Server + ValidationHandler: PROPFIND /ping and lowercase get /ping both abort the connection with a panic at path_item.go:174 (stack: GetOperation <- legacy.FindRoute router.go:150 <- ValidationHandler.validateRequest validation_handler.go:84); the process stays alive and subsequent GET /ping returns 200.

Attack Chain
  1. Entry: unauthenticated PROPFIND /ping HTTP/1.1 (any known static path). Guard: net/http method validation. Bypass proof: net/http accepts any valid HTTP token as a method — PROPFIND and lowercase get both reach the handler.
  2. Route match: legacy/router.go:141 root.Match("PROPFIND /ping"). Guard: trie match. Bypass proof: trie holds only upper-cased canonical methods → miss → route == nil.
  3. route == nil block: router.go:146 doc.Paths.Value("/ping") → non-nil for an existing static path (exact map lookup). Guard: pathItem == nil check (l.147). Bypass proof: static path exists → not nil → proceeds.
  4. Sink: router.go:150 pathItem.GetOperation("PROPFIND") → path_item.go:174 default: panic(...).
  5. Impact: handler goroutine panics; net/http recovers per connection; request never reaches auth.
Bypass Evidence
  • net/http does NOT reject non-standard method tokens — confirmed empirically: PROPFIND and lowercase get both reached the handler and panicked.
  • No canonicalization/rejection exists between FindRoute entry and GetOperation; req.Method is used verbatim (router.go:109).
  • git show v0.144.0:openapi3/path_item.go shows panic(...) at line 174; git show v0.144.0:routers/legacy/router.go shows verbatim method use and the unguarded GetOperation call. git log v0.144.0..HEAD on these files is empty (no post-tag fix).
Scoping Note

The panic requires the request path to be an exact static (non-templated) key in the served spec. A concrete path against a /{id} template returns a clean 500 (not a panic), because Paths.Value is an exact map lookup. Static paths (/health, /login, /version, /metrics, /ping, /) are ubiquitous in real specs and discoverable from public specs/standard endpoints, so a vulnerable target almost always exists. This makes the finding arguably broader than existing panic advisories, as it needs no special spec construct — any static path suffices.

Affected Versions

<= 0.144.0 (present on the latest release tag v0.144.0; long-standing; no patched version).

Suggested Fix

In the legacy router, canonicalize/validate the request method before route resolution and return ErrMethodNotAllowed/ErrPathNotFound for unknown methods, or make PathItem.GetOperation return an error (nil) for unrecognized methods instead of calling panic.


Reported by zx (Jace) — GitHub: @​manus-use

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 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: ValidationHandler security-requirement bypass via encoded-path operation confusion

CVE-2026-100351 / GHSA-23vg-4cjm-66q5

More information

Details

Summary

openapi3filter.ValidationHandler.Middleware validates requests with kin-openapi's
legacy router, which selects an OpenAPI operation from the decoded URL.Path. The
method is documented as implementing gorilla/mux.MiddlewareFunc, but a downstream
gorilla/mux router configured with UseEncodedPath() selects its handler from the
encoded representation (URL.RawPath).

An unauthenticated request such as /public%2Fstatus can therefore be validated as an
intentionally public operation (/public/status) and then dispatched to a protected
operation (/{id}). The configured, rejecting AuthenticationFunc is never invoked,
and the protected downstream handler runs.

This is distinct from GHSA-r277-6w6q-xmqw. The behavior below uses an explicit
authentication callback that rejects missing credentials and remains reproducible
after the v0.144.0 fix for the unsafe default callback.

Affected products
  • Product: github.com/getkin/kin-openapi
  • Component: openapi3filter.ValidationHandler.Middleware
  • Confirmed post-GHSA affected releases: v0.144.0, v0.145.0, and v0.146.0
  • Reporter attestation: each listed release was checked out separately, the attached
    single-file test was copied into openapi3filter/, and that test passed; maintainers
    should independently confirm the version range
  • Confirmed affected development revision: d33e44e67734dcde81b576582185e1343fb3985d
  • v0.143.0 also reproduces this mechanism, but GHSA-r277-6w6q-xmqw already provides a
    simpler bypass on that release; the distinct post-fix claim starts at v0.144.0
  • Earlier releases: not tested; please treat the introduction boundary as unknown
Security impact

An unauthenticated remote requester can reach a handler whose OpenAPI operation
requires authentication when all of the following are true:

  1. the application uses ValidationHandler.Middleware as the security gate;
  2. the downstream router preserves and interprets an encoded path, such as
    gorilla/mux with UseEncodedPath();
  3. a public exact route and a protected parameterized route overlap after decoding;
  4. the public and protected operations expose the same HTTP method;
  5. the protected handler relies on the validation layer's security decision.

The protected operation's path-parameter value is constrained: it is an encoded
representation that decodes to a public literal path (public%2Fstatus in this PoC),
not an arbitrary attacker-selected identifier. A protected operation without a
compatible parameterized path is not reachable through this specific mechanism.

  • Primitive: the security decision is made for a different OpenAPI operation than the
    operation dispatched by the application router.
  • Demonstrated impact: a protected handler executes while the explicitly rejecting
    AuthenticationFunc is never invoked.
  • Impact ceiling: confidentiality or integrity loss when the reachable protected
    handler has that authority; the PoC does not demonstrate data disclosure or state
    modification.
  • Availability impact: not demonstrated.
Root cause

At the confirmed development revision:

  • openapi3filter/validation_handler.go: ValidationHandler.Load constructs
    routers/legacy.NewRouter unconditionally.
  • routers/legacy/router.go: route selection uses the decoded request path.
  • routers/gorillamux/router.go: the kin-openapi gorilla router constructs a
    gorilla/mux router with UseEncodedPath().
  • ValidationHandler.Middleware forwards the unchanged request after validation.

The component making the authentication decision and the component dispatching the
handler can therefore disagree about the selected operation.

Reproduction

The attached PoC uses a real net/http server, an explicit API-key callback that
rejects missing credentials, and a downstream gorilla/mux router with
UseEncodedPath().

From a kin-openapi checkout, copy the self-contained attachment
encoded_path_route_confusion_test.go.txt into openapi3filter/ as
encoded_path_route_confusion_test.go, then run:

go test -v ./openapi3filter -run '^TestEncodedPathAuthorizationBypass$' -count=1

Expected attack observation:

status=202 downstream_route=/{id} protected_id=public%2Fstatus authentication_calls=0
wiring=Router.Use status=202 downstream_route=/{id} protected_id=public%2Fstatus authentication_calls=0

Controls included in the test show that:

  • /ordinary selects the protected operation, invokes authentication once, and is
    rejected with HTTP 401;
  • /public/status reaches only the intentionally public handler;
  • the encoded request reaches the protected /{id} handler with HTTP 202 while the
    authentication callback remains uncalled;
  • both external wrapping and the documented
    router.Use(validationHandler.Middleware) wiring reproduce the same result.
Reverse-proxy reachability

The same request was reproduced through Nginx 1.31.3 using the official
nginx:alpine image digest
sha256:4a73073bd557c65b759505da037898b61f1be6cbcc3c2c3aeac22d2a470c1752.

The exact container invocation was:

docker run --rm --name kin-openapi-nginx \
  --add-host=host.docker.internal:host-gateway \
  -p 18081:8080 -p 18082:8081 \
  -v "$PWD/poc/nginx.conf:/etc/nginx/nginx.conf:ro" \
  nginx:alpine

Container port 8080 uses proxy_pass http://host.docker.internal:18080; without a
replacement URI. Host port 18081 therefore preserved /public%2Fstatus; the protected
handler returned HTTP 202 and the
authentication callback remained at zero calls. With
proxy_pass http://host.docker.internal:18080/; on container port 8081 / host port
18082, Nginx canonicalized the upstream URI; both layers selected /public/status and
the bypass did not occur.

Nginx access-log observations:

server_port=8080 "GET /public%2Fstatus HTTP/1.1" 202 0 "curl/8.7.1"
server_port=8081 "GET /public%2Fstatus HTTP/1.1" 204 0 "curl/8.7.1"
server_port=8080 "GET /ordinary HTTP/1.1" 401 0 "curl/8.7.1"

The response route headers were X-Poc-Route: /{id} on the preserving listener and
X-Poc-Route: /public/status on the canonicalizing listener. Backend observations for
the two requests were:

request_uri="/public%2Fstatus" path="/public/status" raw_path="/public%2Fstatus" auth_before=0
completed request_uri="/public%2Fstatus" auth_after=0
request_uri="/public/status" path="/public/status" raw_path="" auth_before=0
completed request_uri="/public/status" auth_after=0

This control shows that upstream path handling is a deployment precondition rather
than claiming that every proxy configuration is affected.

The canonical protected control produced:

request_uri="/ordinary" path="/ordinary" raw_path="" auth_before=0
completed request_uri="/ordinary" auth_after=1
Suggested remediation

The authentication layer and downstream dispatcher must select the same OpenAPI
operation from the same request representation.

Potential approaches:

  1. make ValidationHandler accept a caller-supplied routers.Router and require it to
    match downstream routing semantics;
  2. document that NewValidator already accepts a router but is safe only when the
    supplied router selects operations using the same semantics as the dispatcher;
    callers must also supply an AuthenticationFunc through ValidationOptions, or
    protected requests fail with the existing missing-authentication-service error;
  3. as an interim hardening option, make ValidationHandler fail closed on an encoded
    path separator when URL.Path and URL.RawPath can select different operations,
    and verify this behavior against the full suite;
  4. document the route-identity requirement and add encoded-separator regression tests.

Replacing the legacy router with the kin-openapi gorilla router closes this PoC, but a
local compatibility check changed two existing missing-path-parameter tests from HTTP
400 semantics to route-not-found HTTP 404 semantics. A direct router swap is therefore
not a fully validated drop-in fix.

Public-reference triage

The public advisory list and related path-encoding reports reviewed on 2026-08-18 did
not describe this authentication-boundary crossing. Relevant nearby reports include
issues #​397, #​1089, and #​1105 and PR #​400. This search is not proof of novelty; please
deduplicate against private reports.

Credit

Preferred credit: Satoru Kanno (GMO CONNECT Inc., GMO Internet Group, Inc.)

Severity

  • CVSS Score: 7.4 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:H/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: YAML NaN/Inf Values Bypass Built-in Guards and Panic via OAS 3.1 Validation Path

CVE-2026-100352 / GHSA-rj39-cv6w-8wv7

More information

Details

Summary

kin-openapi guards against NaN and Inf values in its built-in schema visitor (visitJSON) by returning ErrSchemaInputNaN / ErrSchemaInputInf when a float64 input violates those constraints. For OAS 3.1 specs, however, Schema.VisitJSON routes through useJSONSchema2020, which hands the value directly to santhosh-tekuri/jsonschema/v6 v6.0.2 without applying the same guard.

Inside jsonschema/v6 v6.0.2, numValidate builds a *big.Rat from the input via big.Rat.SetString(fmt.Sprintf("%v", v)). For NaN/Inf this parse fails silently and returns nil. numValidate then dereferences the nil *big.Rat (in math/big.(*Rat).Cmp), producing a Go runtime panic.

An attacker delivers this by sending an HTTP request with Content-Type: application/yaml (or application/x-yaml) and a YAML body containing .nan, .inf, or -.inf. The YamlBodyDecoder decodes those tokens into math.NaN() / math.Inf(±1) (standard YAML 1.2 behavior), and they propagate to the unguarded useJSONSchema2020 path whenever the schema declares a numeric constraint (minimum, maximum, exclusiveMinimum, exclusiveMaximum, or multipleOf).

Details
Root Cause

The built-in visitor visitJSON at openapi3/schema.go:1968 guards NaN/Inf explicitly:

case float64:
    if math.IsNaN(value) {
        return ErrSchemaInputNaN
    }
    if math.IsInf(value, 0) {
        return ErrSchemaInputInf
    }

For OAS 3.1 specs, VisitJSON dispatches to useJSONSchema2020 (openapi3/schema_jsonschema_validator.go:204), which contains no such guard:

func (schema *Schema) useJSONSchema2020(settings *schemaValidationSettings, value any) error {
    validator, err := newJSONSchemaValidator(schema)
    if err != nil {
        return schema.visitJSON(settings, value)
    }
    return validator.validate(value)      // value handed straight through
}

jsonSchemaValidator.validate calls jsonschema/v6.Schema.Validate (line 146), which reaches numValidate in v6.0.2. There, big.Rat.SetString(fmt.Sprintf("%v", v)) returns (nil, false) for NaN/Inf, and the code dereferences the nil *big.Rat in Rat.Cmp, causing the panic.

Proof of Concept
Environment
  • kin-openapi v0.144.0 (tag v0.144.0, commit f0407d5)
  • santhosh-tekuri/jsonschema/v6 v6.0.2 (transitive dep)
  • Go 1.26.7
Spec

Minimal OAS 3.1 spec (spec.yaml) declaring a YAML body with a numeric constraint:

openapi: '3.1.0'
info: { title: Test, version: '1.0' }
paths:
  /test:
    post:
      requestBody:
        required: true
        content:
          application/yaml:
            schema:
              type: number
              minimum: 0
      responses:
        '200': { description: OK }
Trigger

A single POST /test with Content-Type: application/yaml and body .nan, .inf, or -.inf. All three variants panic:

=== RUN   TestNaNInf
[nan]    PANIC (confirms vuln): runtime error: invalid memory address or nil pointer dereference
[inf]    PANIC (confirms vuln): runtime error: invalid memory address or nil pointer dereference
[neginf] PANIC (confirms vuln): runtime error: invalid memory address or nil pointer dereference
Panic stack trace (confirms exact chain)
panic: runtime error: invalid memory address or nil pointer dereference

math/big.(*Rat).Cmp(0x0, ...)                                       <- nil *big.Rat dereferenced
    C:/Program Files/Go/src/math/big/rat.go:492
github.com/santhosh-tekuri/jsonschema/v6.(*validator).numValidate(...)
    .../jsonschema/v6@v6.0.2/validator.go:525
github.com/santhosh-tekuri/jsonschema/v6.(*validator).validate(...)
    .../jsonschema/v6@v6.0.2/validator.go:168
github.com/santhosh-tekuri/jsonschema/v6.(*Schema).validate(...)
    .../jsonschema/v6@v6.0.2/validator.go:34
github.com/getkin/kin-openapi/openapi3.(*jsonSchemaValidator).validate(...)
    openapi3/schema_jsonschema_validator.go:146
github.com/getkin/kin-openapi/openapi3.(*Schema).useJSONSchema2020(...)   <- MISSING NaN/Inf guard
    openapi3/schema_jsonschema_validator.go:211
github.com/getkin/kin-openapi/openapi3.(*Schema).VisitJSON(...)
    openapi3/schema.go:1946
github.com/getkin/kin-openapi/openapi3filter.ValidateRequestBody(...)
    openapi3filter/validate_request.go:364
github.com/getkin/kin-openapi/openapi3filter.ValidateRequest(...)
    openapi3filter/validate_request.go:96
net/http behavior (severity nuance)

Behind a standard net/http server, the panic is recovered per-connection. The client sees a dropped connection; the server process continues:

Sending POST /test body=.nan against real net/http server...
2026/08/25 http: panic serving 127.0.0.1:40625: runtime error: invalid memory address or nil pointer dereference
CLIENT ERROR (connection dropped): Post ".../test": EOF
Process still alive after request: true

This is a recoverable panic, unlike the recursion finding. recover() catches it (verified by capturing the panic value in a defer in the same goroutine). Non-net/http hosts or validation performed in an application-spawned goroutine are not covered by that recovery and would experience a full-process crash.

Fix verification

Upgrading only the transitive dependency to jsonschema/v6@v6.0.3 (via go get github.com/santhosh-tekuri/jsonschema/v6@v6.0.3) eliminates the panic entirely; all three payloads return a clean validation error:

[nan]    Returned normally, err = request body has an error: doesn't match schema: validation failed due to: at '': invalid jsonType float64
[inf]    Returned normally, err = request body has an error: doesn't match schema: validation failed due to: at '': invalid jsonType float64
[neginf] Returned normally, err = request body has an error: doesn't match schema: validation failed due to: at '': invalid jsonType float64

This confirms the upstream jsonschema/v6 v6.0.3 release treats the NaN/Inf case as a bug and fixes it at the source. It also confirms useJSONSchema2020 is the reachable path: patching only the transitive dep removes the panic without touching kin-openapi code.

Why This Is a Vulnerability, Not by Design
  • The library explicitly refuses NaN/Inf in its own visitor (ErrSchemaInputNaN / ErrSchemaInputInf at schema.go:1968). The stated policy is: these values are not allowed.
  • The OAS 3.1 dispatch (useJSONSchema2020) simply omits the same guard — a coding gap, not a design decision.
  • Upstream jsonschema/v6 v6.0.3 fixes the same case as a bug. Two independent code owners agree it should not panic.
  • The trigger is a single HTTP request body an unauthenticated client can send. There is no legitimate content-authoring reason to submit .nan / .inf as a numeric field.
Impact
  • Per-request denial of service (net/http hosts). Every crafted request kills its own connection with zero attacker cost. Sustained flooding degrades service reliability and log noise.
  • Full-process denial of service (non-net/http hosts or goroutine-spawned validation). No net/http recovery wrapper is present; the whole process crashes on a single request.
  • Attack surface is any OAS 3.1 API that accepts YAML bodies with numeric constraints. These constraints (minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf) are common on any numeric field with a range.

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: Uncontrolled Recursion on Circular allOf/anyOf/oneOf Schemas Causes Fatal Process Crash

CVE-2026-100354 / GHSA-g8cf-rcjf-c8cf

More information

Details

Summary

kin-openapi's spec loader and spec-time validator both handle circular schema references safely: the loader resolves circular $refs on purpose (to support recursive data structures), and Schema.validate() uses stack-based cycle detection. Validate() therefore accepts a schema whose allOf / anyOf / oneOf chain refers back to itself, returning no error.

The runtime validators do not have that protection. When such a schema is used for request or response validation, the recursion through the combinator chain is unbounded. In Go this produces a fatal stack overflow (runtime: goroutine stack exceeds ... limit / fatal error: stack overflow), which is not a recoverable panic. recover() cannot catch it, so net/http's per-connection recovery does not help. The entire process is terminated, including all goroutines serving unrelated clients.

Two independent unguarded recursion sites were confirmed:

  1. openapi3/schema.go — visitJSON / visitXOFOperations (reached by VisitJSON, i.e. response validation and direct schema validation).
  2. openapi3filter/req_resp_decoder.go — decodeValue (reached first during request parameter decoding). The report also identifies the same gap in urlValuesDecoder.parseValue and buildResObj/buildFromSchemas.
Details
Root Cause

Schema.validate() has cycle detection (openapi3/schema.go:1396):

func (schema *Schema) validate(ctx context.Context, stack []*Schema) ([]*Schema, error) {
	if slices.Contains(stack, schema) {
		return stack, nil
	}
	// ...
	stack = append(stack, schema)

The runtime visitor has none. visitJSON (line 1951) calls visitXOFOperations (line 2189), which iterates the combinator slice and calls visitJSON again on each sub-schema:

func (schema *Schema) visitXOFOperations(settings *schemaValidationSettings, value any) (err error, run bool) {
	// ...
	for idx, item := range v { // v = schema.OneOf / AnyOf / AllOf
		v := item.Value
		// no check for item.Value == schema (or any ancestor)
		if err := v.visitJSON(settings, tempValue); err != nil { // recurses forever on a cycle

The request-decoding path has the same structure in decodeValue (openapi3filter/req_resp_decoder.go:270):

func decodeValue(dec valueDecoder, param string, sm *openapi3.SerializationMethod, schema *openapi3.SchemaRef, required bool) (any, bool, error) {
	if len(schema.Value.AllOf) > 0 {
		for _, sr := range schema.Value.AllOf {
			value, f, err = decodeValue(dec, param, sm, sr, required) // no cycle check
Proof of Concept
Environment
  • kin-openapi v0.144.0 (tag v0.144.0, commit f0407d5)
  • Go 1.26.7
Path A: response / direct schema validation (VisitJSON)

A schema whose allOf refers to itself. Validate() is called first to show the schema is accepted, then VisitJSON():

recursiveSchema := &openapi3.Schema{
	Type:       &openapi3.Types{"object"},
	Properties: openapi3.Schemas{"name": {Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}}},
}
recursiveSchema.AllOf = openapi3.SchemaRefs{{Value: recursiveSchema}} // circular

_ = recursiveSchema.Validate(ctx)                       // accepted, no error
_ = recursiveSchema.VisitJSON(map[string]any{"name": "test"}) // crashes

Output:

Validate() ACCEPTED the circular schema (no error) -- spec-time cycle detection tolerates it
Calling VisitJSON() -- expect fatal stack overflow (unrecoverable)...
runtime: goroutine stack exceeds 1000000000-byte limit
fatal error: stack overflow

goroutine 1 [running]:
github.com/getkin/kin-openapi/openapi3.(*Schema).visitJSON(...)
	openapi3/schema.go:1951
github.com/getkin/kin-openapi/openapi3.(*Schema).visitXOFOperations(...)
	openapi3/schema.go:2309
github.com/getkin/kin-openapi/openapi3.(*Schema).visitJSON(...)
	openapi3/schema.go:1981
github.com/getkin/kin-openapi/openapi3.(*Schema).visitXOFOperations(...)
	openapi3/schema.go:2309
... (repeats until stack overflow)

A defer recover() placed around the call did not catch the fatal error — the process aborted with a non-zero exit code, confirming it is unrecoverable.

Path B: HTTP request validation against a loaded spec (realistic delivery)

A legal OAS 3.0 spec with a circular allOf $ref, loaded normally, served behind a standard net/http handler using openapi3filter.ValidateRequest:

openapi: '3.0.0'
info: { title: Circular allOf crash PoC, version: '1.0' }
paths:
  /test:
    get:
      parameters:
        - name: data
          in: query
          schema: { $ref: '#/components/schemas/Recursive' }
      responses: { '200': { description: OK } }
components:
  schemas:
    Recursive:
      type: object
      allOf:
        - $ref: '#/components/schemas/Recursive'
      properties:
        name: { type: string }

A single GET /test?data=x crashes the whole server process:

Validate() ACCEPTED the spec with circular allOf $ref
Sending GET /test?data=x through a real net/http server...
runtime: goroutine stack exceeds 1000000000-byte limit
fatal error: stack overflow
...
github.com/getkin/kin-openapi/openapi3filter.decodeValue(...)
	openapi3filter/req_resp_decoder.go:278
github.com/getkin/kin-openapi/openapi3filter.decodeValue(...)
	openapi3filter/req_resp_decoder.go:278
... (repeats until stack overflow)

The line printed after the request ("the process survived") was never reached, and the process exited with a non-zero code — the standard net/http per-connection recover() did not save it. This is the key distinction from a recoverable panic: a fatal stack overflow takes the entire process down regardless of host.

Fix verification

Adding stack-based cycle detection to both sites (matching validate()'s pattern) eliminated the crash:

  • visitJSON guard: VisitJSON() returned normally instead of crashing.
  • decodeValue guard: the same GET /test?data=x returned a clean 400:
Response: 400 "invalid: parameter \"data\" in query has an error: circular schema reference detected while decoding"
If you see this line, the process survived.
Impact

An unrecoverable, whole-process denial of service. Because the crash is a fatal Go runtime error (not a panic), it cannot be contained by net/http's per-request recovery, so A:H applies to all host types, unlike a recoverable-panic bug.

Attack surface, in order of exposure:

  • Applications that load OpenAPI specs from untrusted or semi-trusted sources (API gateways, spec linters/validators, mock servers, documentation generators). An attacker supplies a spec containing a circular combinator, and any subsequent request/response validation crashes the service. This is the primary remote-attacker scenario.
  • Applications serving a fixed, trusted spec that happens to contain a circular allOf/anyOf/oneOf. Here any client request to the affected endpoint crashes the process. The spec author does not need malicious intent; a degenerate recursive combinator is enough. This is a latent reliability bug that becomes remotely triggerable once present.

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


Release Notes

getkin/kin-openapi (github.com/getkin/kin-openapi)

v0.149.0

Compare Source

What's Changed

Full Changelog: getkin/kin-openapi@v0.148.0...v0.149.0

v0.148.0

Compare Source

What's Changed

New Contributors

Full Changelog: getkin/kin-openapi@v0.147.0...v0.148.0

v0.147.0

Compare Source

What's Changed

New Contributors

Full Changelog: getkin/kin-openapi@v0.146.0...v0.147.0

v0.146.0

Compare Source

What's Changed

Full Changelog: getkin/kin-openapi@v0.145.0...v0.146.0

v0.145.0

Compare Source

What's Changed

Full Changelog: getkin/kin-openapi@v0.144.0...v0.145.0


Configuration

📅 Schedule: (UTC)

  • Branch creation
    • At any time (no schedule defined)
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Enabled.

♻ Rebasing: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovate renovate Bot added the deps label Oct 15, 2026
@renovate
renovate Bot enabled auto-merge (squash) October 15, 2026 23:22
@codecov

codecov Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.08%. Comparing base (d21bda7) to head (2c0aedb).

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #569   +/-   ##
=======================================
  Coverage   83.08%   83.08%           
=======================================
  Files        1200     1200           
  Lines      123542   123542           
=======================================
+ Hits       102639   102642    +3     
+ Misses      20895    20892    -3     
  Partials        8        8           

see 13 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@renovate
renovate Bot force-pushed the renovate/go-github.com-getkin-kin-openapi-vulnerability branch from 196a1f8 to 2c0aedb Compare October 16, 2026 03:27
@renovate
renovate Bot merged commit 741910f into main Oct 16, 2026
26 checks passed
@renovate
renovate Bot deleted the renovate/go-github.com-getkin-kin-openapi-vulnerability branch October 16, 2026 03:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants