Summary
When schema_validation.ValidateOpenAPIDocument builds a SchemaValidationFailure, it renders the located node with yaml.Marshal(located) (schema_validation/validate_document.go, around line 381 in v0.14.0). located is a node from the document's own tree, not a copy.
go.yaml.in/yaml/v4 (v4.0.0-rc.6) mutates the node it is given while encoding: it rewrites Tag and Style on it and its descendants. So rendering a violation writes to the caller's tree.
In vacuum this tree is shared. oas3-schema (which calls ValidateOpenAPIDocument) and oas3-valid-schema-example run concurrently in the motor's worker pool and read the same nodes. That has two effects:
go test -race reports a data race between the two rules. Our code does not appear in the stack.
- The results depend on which rule runs first. On one real-world spec (~2.5k lines, OpenAPI 3.1), 40 runs of the recommended ruleset produced 3–4 distinct result sets. One
oas3-valid-schema-example finding ("got object, want null", on a schema with allOf: [$ref] plus type: null) appears in some runs and not in others, and the order of oas3-schema sub-errors changes. Specs that produce no oas3-schema violations are fully stable, which fits the explanation that the mutation only happens on the error-rendering path.
Expected
Rendering a violation should not modify the document being validated. It should be read-only on the input tree.
Suggested fix
Marshal a deep copy of located instead of the node itself. Alternatively, render it without going through the encoder's tag/style resolution.
Versions
- github.com/pb33f/libopenapi v0.38.7
- github.com/pb33f/libopenapi-validator v0.14.0
- github.com/daveshanley/vacuum v0.30.6
- go.yaml.in/yaml/v4 v4.0.0-rc.6
- Go 1.26.0 (darwin/arm64 locally, linux in CI)
Reproduction
Any OpenAPI 3.x document with a schema violation that oas3-schema reports, linted by vacuum's recommended ruleset via motor.ApplyRulesToRuleSet under go test -race. I can provide a minimised spec if that helps. The real one is internal.
cc @daveshanley, since this shows up through vacuum's motor.
Summary
When
schema_validation.ValidateOpenAPIDocumentbuilds aSchemaValidationFailure, it renders the located node withyaml.Marshal(located)(schema_validation/validate_document.go, around line 381 in v0.14.0).locatedis a node from the document's own tree, not a copy.go.yaml.in/yaml/v4(v4.0.0-rc.6) mutates the node it is given while encoding: it rewritesTagandStyleon it and its descendants. So rendering a violation writes to the caller's tree.In vacuum this tree is shared.
oas3-schema(which callsValidateOpenAPIDocument) andoas3-valid-schema-examplerun concurrently in the motor's worker pool and read the same nodes. That has two effects:go test -racereports a data race between the two rules. Our code does not appear in the stack.oas3-valid-schema-examplefinding ("got object, want null", on a schema withallOf: [$ref]plustype: null) appears in some runs and not in others, and the order ofoas3-schemasub-errors changes. Specs that produce nooas3-schemaviolations are fully stable, which fits the explanation that the mutation only happens on the error-rendering path.Expected
Rendering a violation should not modify the document being validated. It should be read-only on the input tree.
Suggested fix
Marshal a deep copy of
locatedinstead of the node itself. Alternatively, render it without going through the encoder's tag/style resolution.Versions
Reproduction
Any OpenAPI 3.x document with a schema violation that
oas3-schemareports, linted by vacuum's recommended ruleset viamotor.ApplyRulesToRuleSetundergo test -race. I can provide a minimised spec if that helps. The real one is internal.cc @daveshanley, since this shows up through vacuum's motor.