Skip to content

Commit 330bacc

Browse files
committed
fix: resolve stack overflow with circular schema references
kin-openapi's Validate() crashes with a stack overflow when schemas have circular $ref references (e.g. discriminator/inheritance patterns). The crash occurs in the visitJSON codepath which validates default/example values against schemas without cycle detection. - slice/split: pass DisableSchemaDefaultsValidation and DisableExamplesValidation to Validate() to skip the unsafe visitJSON codepath while keeping structural schema validation - filter: resolve $ref pointers in duplicateOas using ResolveRefsIn so that the duplicate is a proper kin-openapi object with populated .Value fields; regenerated golden test files accordingly - extension: skip recursing into $ref SchemaRefs in updateExtensionsForSchema to avoid infinite recursion on circular refs - versioning: skip $ref responses in updateResponses to avoid mutating shared component responses during per-operation versioning
1 parent d5d9433 commit 330bacc

45 files changed

Lines changed: 1482700 additions & 606603 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

tools/cli/internal/cli/slice/slice.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"fmt"
2020
"log"
2121

22+
"github.com/getkin/kin-openapi/openapi3"
2223
"github.com/mongodb/openapi/tools/cli/internal/cli/flag"
2324
"github.com/mongodb/openapi/tools/cli/internal/cli/usage"
2425
"github.com/mongodb/openapi/tools/cli/internal/openapi"
@@ -66,8 +67,13 @@ func (o *Opts) Run() error {
6667
return err
6768
}
6869

69-
// Validate the sliced spec
70-
if err := specInfo.Spec.Validate(loader.Loader.Context); err != nil {
70+
// Validate the sliced spec structure (skip defaults/examples validation
71+
// to avoid stack overflow with circular schema references in kin-openapi).
72+
ctx := openapi3.WithValidationOptions(loader.Loader.Context,
73+
openapi3.DisableSchemaDefaultsValidation(),
74+
openapi3.DisableExamplesValidation(),
75+
)
76+
if err := specInfo.Spec.Validate(ctx); err != nil {
7177
log.Printf("[WARN] Sliced OpenAPI document has validation warnings: %v", err)
7278
}
7379

tools/cli/internal/cli/split/split.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,13 @@ func (o *Opts) Run() error {
6565
return err
6666
}
6767

68-
if err := filteredOAS.Validate(loader.Loader.Context); err != nil {
68+
// Validate the spec structure (skip defaults/examples validation
69+
// to avoid stack overflow with circular schema references in kin-openapi).
70+
ctx := openapi3.WithValidationOptions(loader.Loader.Context,
71+
openapi3.DisableSchemaDefaultsValidation(),
72+
openapi3.DisableExamplesValidation(),
73+
)
74+
if err := filteredOAS.Validate(ctx); err != nil {
6975
log.Printf("[WARN] OpenAPI document is invalid: %v", err)
7076
}
7177
}

tools/cli/internal/openapi/filter/extension.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,12 @@ func (f *ExtensionFilter) updateExtensionsForSchema(schema *openapi3.SchemaRef)
156156
}
157157
f.deleteIpaExceptionExtensionIfNeeded(schema.Extensions)
158158

159+
// If this is a $ref, don't recurse into .Value — the referenced schema
160+
// will be visited via updateExtensionsForComponents.
161+
if isRef(schema) {
162+
return
163+
}
164+
159165
if schema.Value == nil {
160166
return
161167
}

tools/cli/internal/openapi/filter/filter.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,11 +155,20 @@ func duplicateOas(doc *openapi3.T) (*openapi3.T, error) {
155155

156156
// Unmarshal the JSON data into a new OpenAPI document
157157
duplicateDoc := &openapi3.T{}
158-
err = json.Unmarshal(jsonData, duplicateDoc)
159-
if err != nil {
158+
if err = json.Unmarshal(jsonData, duplicateDoc); err != nil {
160159
return nil, fmt.Errorf("failed to unmarshal duplicated OpenAPI specification: %w", err)
161160
}
162161

162+
// Resolve $ref pointers so that SchemaRef.Value (and other ref types) are
163+
// populated. Without this, filters that walk schemas would see nil .Value
164+
// on every $ref. We use ResolveRefsIn rather than LoadFromData because the
165+
// loader's full pipeline normalizes media types and other structures, which
166+
// would change the output.
167+
loader := openapi3.NewLoader()
168+
if err = loader.ResolveRefsIn(duplicateDoc, nil); err != nil {
169+
return nil, fmt.Errorf("failed to resolve refs in duplicated OpenAPI specification: %w", err)
170+
}
171+
163172
return duplicateDoc, nil
164173
}
165174

tools/cli/internal/openapi/filter/versioning.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,12 @@ func updateResponses(op *openapi3.Operation, config *VersionConfig) error {
134134
continue
135135
}
136136

137+
// Skip $ref responses — they point to shared component responses
138+
// that should not be mutated by per-operation versioning filters.
139+
if response.Ref != "" {
140+
continue
141+
}
142+
137143
filteredResponse, err := filterResponse(response, op, config)
138144
if err != nil {
139145
return err

0 commit comments

Comments
 (0)