Skip to content

Recursion guard - #306

Merged
fredbi merged 2 commits into
go-openapi:masterfrom
fredbi:recursion-guard
Oct 20, 2026
Merged

fredbi merged 2 commits into
go-openapi:masterfrom
fredbi:recursion-guard

Conversation

@fredbi

@fredbi fredbi commented Oct 20, 2026 •

Copy link
Copy Markdown
Member

Change type

Please select: 🆕 New feature or enhancement|🔧 Bug fix'|📃 Documentation update

Short description

Fixes

Full description

Checklist

  • I have signed all my commits with my name and email (see DCO. This does not require a PGP-signed commit
  • I have rebased and squashed my work, so only one commit remains
  • I have added tests to cover my changes.
  • I have properly enriched go doc comments in code.
  • I have properly documented any breaking change.

fredbi and others added 2 commits October 20, 2026 21:40
With recycled validators, SchemaValidator.Validate and the allOf, anyOf,
oneOf and not loops of schemaPropsValidator cleared the slot of a child
only after the child's Validate returned. A child redeems itself in a
deferred call, so when its Validate panicked (e.g. a $ref under a
property that cannot be expanded), the parent's deferred redeemChildren
redeemed it a second time.

The pool then hands the same object to two later borrowers. A server
survives the panic (net/http recovers handler panics), so later
requests could share validator state and return wrong results.

Clear the slot before calling Validate. TestPools_NoDoubleRedeemOnPanic
reproduces it with -tags poolsdebug, which reports the double redeem.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Frédéric BIDON <fredbi@yahoo.com>
A circular $ref through a composition keyword, such as
A: {allOf: [{$ref: A}]}, made schema validation overflow the goroutine
stack: a fatal error that recover cannot catch.

go-openapi/spec bounds each expansion, and leaves a circular $ref in
place. But newSchemaPropsValidator builds the allOf, anyOf, oneOf and
not validators along with their parent, and each one expands its $ref
again, one level down, in a new expansion. Nothing bounded that loop.
With two branches, A: {allOf: [{$ref: A}, {$ref: A}]}, it builds
2^depth validators.

Thread a compositionBudget through validator construction. It caps the
nesting at maxCompositionDepth (128) and the count at
maxCompositionValidators (10,000) per schema validator. Going over
either cap panics with "Invalid schema provided to SchemaValidator",
like any other schema that cannot be expanded. Only schemas with
composition keywords create a budget, and it stays on the stack: no
pre-walk, no allocation. Validators for properties and items are built
as Validate walks the data, and start their own budget, so recursive
schemas such as trees are not affected.

SpecValidator no longer panics on such a schema when it validates
defaults and examples (reached with ContinueOnErrors, since the
circular ancestry check stops it otherwise): validateValueAgainstSchema
returns the panic as an error at the location of the value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Frédéric BIDON <fredbi@yahoo.com>
@fredbi
fredbi merged commit 16f7a05 into go-openapi:master Oct 20, 2026
22 of 23 checks passed
@fredbi
fredbi deleted the recursion-guard branch October 20, 2026 20:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant