Skip to content

Commit b903811

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
1 parent d5d9433 commit b903811

58 files changed

Lines changed: 1481633 additions & 607830 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/test/data/split/dev/openapi-v2-2023-01-01.json

Lines changed: 0 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -203,51 +203,12 @@
203203
"description": "Accepted."
204204
},
205205
"badRequest": {
206-
"content": {
207-
"application/json": {
208-
"example": {
209-
"detail": "(This is just an example, the exception may not be related to this endpoint) No provider AWS exists.",
210-
"error": 400,
211-
"errorCode": "VALIDATION_ERROR",
212-
"reason": "Bad Request"
213-
},
214-
"schema": {
215-
"$ref": "#/components/schemas/ApiError"
216-
}
217-
}
218-
},
219206
"description": "Bad Request."
220207
},
221208
"conflict": {
222-
"content": {
223-
"application/json": {
224-
"example": {
225-
"detail": "(This is just an example, the exception may not be related to this endpoint) Cannot delete organization link while there is active migration in following project ids: 60c4fd418ebe251047c50554",
226-
"error": 409,
227-
"errorCode": "CANNOT_DELETE_ORG_ACTIVE_LIVE_MIGRATION_ATLAS_ORG_LINK",
228-
"reason": "Conflict"
229-
},
230-
"schema": {
231-
"$ref": "#/components/schemas/ApiError"
232-
}
233-
}
234-
},
235209
"description": "Conflict."
236210
},
237211
"forbidden": {
238-
"content": {
239-
"application/json": {
240-
"example": {
241-
"detail": "(This is just an example, the exception may not be related to this endpoint)",
242-
"error": 403,
243-
"errorCode": "CANNOT_CHANGE_GROUP_NAME",
244-
"reason": "Forbidden"
245-
},
246-
"schema": {
247-
"$ref": "#/components/schemas/ApiError"
248-
}
249-
}
250-
},
251212
"description": "Forbidden."
252213
},
253214
"gone": {
@@ -267,86 +228,21 @@
267228
"description": "Gone."
268229
},
269230
"internalServerError": {
270-
"content": {
271-
"application/json": {
272-
"example": {
273-
"detail": "(This is just an example, the exception may not be related to this endpoint)",
274-
"error": 500,
275-
"errorCode": "UNEXPECTED_ERROR",
276-
"reason": "Internal Server Error"
277-
},
278-
"schema": {
279-
"$ref": "#/components/schemas/ApiError"
280-
}
281-
}
282-
},
283231
"description": "Internal Server Error."
284232
},
285233
"methodNotAllowed": {
286-
"content": {
287-
"application/json": {
288-
"example": {
289-
"detail": "(This is just an example, the exception may not be related to this endpoint)",
290-
"error": 405,
291-
"errorCode": "ATLAS_BACKUP_CANCEL_SHARD_RESTORE_JOB_NOT_ALLOWED",
292-
"reason": "Method Not Allowed"
293-
},
294-
"schema": {
295-
"$ref": "#/components/schemas/ApiError"
296-
}
297-
}
298-
},
299234
"description": "Method Not Allowed."
300235
},
301236
"noBody": {
302237
"description": "This endpoint does not return a response body."
303238
},
304239
"notFound": {
305-
"content": {
306-
"application/json": {
307-
"example": {
308-
"detail": "(This is just an example, the exception may not be related to this endpoint) Cannot find resource AWS",
309-
"error": 404,
310-
"errorCode": "RESOURCE_NOT_FOUND",
311-
"reason": "Not Found"
312-
},
313-
"schema": {
314-
"$ref": "#/components/schemas/ApiError"
315-
}
316-
}
317-
},
318240
"description": "Not Found."
319241
},
320242
"paymentRequired": {
321-
"content": {
322-
"application/json": {
323-
"example": {
324-
"detail": "(This is just an example, the exception may not be related to this endpoint)",
325-
"error": 402,
326-
"errorCode": "NO_PAYMENT_INFORMATION_FOUND",
327-
"reason": "Payment Required"
328-
},
329-
"schema": {
330-
"$ref": "#/components/schemas/ApiError"
331-
}
332-
}
333-
},
334243
"description": "Payment Required."
335244
},
336245
"unauthorized": {
337-
"content": {
338-
"application/json": {
339-
"example": {
340-
"detail": "(This is just an example, the exception may not be related to this endpoint)",
341-
"error": 401,
342-
"errorCode": "NOT_ORG_GROUP_CREATOR",
343-
"reason": "Unauthorized"
344-
},
345-
"schema": {
346-
"$ref": "#/components/schemas/ApiError"
347-
}
348-
}
349-
},
350246
"description": "Unauthorized."
351247
}
352248
},

tools/cli/test/data/split/dev/openapi-v2-2023-01-01.yaml

Lines changed: 0 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -167,37 +167,10 @@ components:
167167
accepted:
168168
description: Accepted.
169169
badRequest:
170-
content:
171-
application/json:
172-
example:
173-
detail: (This is just an example, the exception may not be related to this endpoint) No provider AWS exists.
174-
error: 400
175-
errorCode: VALIDATION_ERROR
176-
reason: Bad Request
177-
schema:
178-
$ref: '#/components/schemas/ApiError'
179170
description: Bad Request.
180171
conflict:
181-
content:
182-
application/json:
183-
example:
184-
detail: '(This is just an example, the exception may not be related to this endpoint) Cannot delete organization link while there is active migration in following project ids: 60c4fd418ebe251047c50554'
185-
error: 409
186-
errorCode: CANNOT_DELETE_ORG_ACTIVE_LIVE_MIGRATION_ATLAS_ORG_LINK
187-
reason: Conflict
188-
schema:
189-
$ref: '#/components/schemas/ApiError'
190172
description: Conflict.
191173
forbidden:
192-
content:
193-
application/json:
194-
example:
195-
detail: (This is just an example, the exception may not be related to this endpoint)
196-
error: 403
197-
errorCode: CANNOT_CHANGE_GROUP_NAME
198-
reason: Forbidden
199-
schema:
200-
$ref: '#/components/schemas/ApiError'
201174
description: Forbidden.
202175
gone:
203176
content:
@@ -211,61 +184,16 @@ components:
211184
$ref: '#/components/schemas/ApiError'
212185
description: Gone.
213186
internalServerError:
214-
content:
215-
application/json:
216-
example:
217-
detail: (This is just an example, the exception may not be related to this endpoint)
218-
error: 500
219-
errorCode: UNEXPECTED_ERROR
220-
reason: Internal Server Error
221-
schema:
222-
$ref: '#/components/schemas/ApiError'
223187
description: Internal Server Error.
224188
methodNotAllowed:
225-
content:
226-
application/json:
227-
example:
228-
detail: (This is just an example, the exception may not be related to this endpoint)
229-
error: 405
230-
errorCode: ATLAS_BACKUP_CANCEL_SHARD_RESTORE_JOB_NOT_ALLOWED
231-
reason: Method Not Allowed
232-
schema:
233-
$ref: '#/components/schemas/ApiError'
234189
description: Method Not Allowed.
235190
noBody:
236191
description: This endpoint does not return a response body.
237192
notFound:
238-
content:
239-
application/json:
240-
example:
241-
detail: (This is just an example, the exception may not be related to this endpoint) Cannot find resource AWS
242-
error: 404
243-
errorCode: RESOURCE_NOT_FOUND
244-
reason: Not Found
245-
schema:
246-
$ref: '#/components/schemas/ApiError'
247193
description: Not Found.
248194
paymentRequired:
249-
content:
250-
application/json:
251-
example:
252-
detail: (This is just an example, the exception may not be related to this endpoint)
253-
error: 402
254-
errorCode: NO_PAYMENT_INFORMATION_FOUND
255-
reason: Payment Required
256-
schema:
257-
$ref: '#/components/schemas/ApiError'
258195
description: Payment Required.
259196
unauthorized:
260-
content:
261-
application/json:
262-
example:
263-
detail: (This is just an example, the exception may not be related to this endpoint)
264-
error: 401
265-
errorCode: NOT_ORG_GROUP_CREATOR
266-
reason: Unauthorized
267-
schema:
268-
$ref: '#/components/schemas/ApiError'
269197
description: Unauthorized.
270198
schemas:
271199
AWSCloudProviderContainer:

0 commit comments

Comments
 (0)