Skip to content

Failed anyOf/oneOf branches suppress unevaluated constraints and cause false acceptance #117

Description

@MicroMilo

Summary

The anyOf validator accepts only a successful branch, and oneOf explicitly computes the unique successful match. In contrast, isItemEvaluated and isPropertyEvaluated inspect every anyOf/oneOf branch without checking branch validity. An annotation-producing but failed branch can therefore suppress unevaluatedItems or unevaluatedProperties enforcement even though only a non-annotating branch succeeded.

Impact: A failed branch containing items:true or additionalProperties:true marks values as evaluated; when a separate non-annotating branch satisfies anyOf/oneOf, unevaluatedItems:false or unevaluatedProperties:false is skipped and invalid instances return valid:true.

Code path

Checked against current main at commit 1cc2a3045f615ef67104f02449c6fd58afebeb6a.

  • src/keywords/anyOf.ts:71-80
  • src/keywords/oneOf.ts:76-88
  • src/isItemEvaluated.ts:44-58
  • src/isPropertyEvaluated.ts:52-66

Steps to reproduce

Validation level: current HEAD supplemental dynamic witness reproduced.

node -e 'const fs=require("fs"),vm=require("vm");vm.runInThisContext(fs.readFileSync("dist/jlib.js","utf8")); for(const combinator of ["anyOf","oneOf"]){for(const kind of ["items","properties"]){const annotatingInvalid=kind==="items"?{items:true,maxItems:0}:{additionalProperties:true,maxProperties:0}; const validNonAnnotating={type:kind==="items"?"array":"object"}; const schema={$schema:"https://json-schema.org/draft/2020-12/schema",[combinator]:[annotatingInvalid,validNonAnnotating],[kind==="items"?"unevaluatedItems":"unevaluatedProperties"]:false}; const data=kind==="items"?[1]:{x:1}; const result=jlib.compileSchema(schema).validate(data); console.log(JSON.stringify({combinator,kind,valid:result.valid,errors:result.errors.map(e=>e.code)}));}}'

Observed output:

{"combinator":"anyOf","kind":"items","valid":true,"errors":[]}
{"combinator":"anyOf","kind":"properties","valid":true,"errors":[]}
{"combinator":"oneOf","kind":"items","valid":true,"errors":[]}
{"combinator":"oneOf","kind":"properties","valid":true,"errors":[]}

Neighboring control:

node -e 'const fs=require("fs"),vm=require("vm");vm.runInThisContext(fs.readFileSync("dist/jlib.js","utf8")); for(const combinator of ["anyOf","oneOf"]){for(const kind of ["items","properties"]){const validNonAnnotating={type:kind==="items"?"array":"object"}; const schema={$schema:"https://json-schema.org/draft/2020-12/schema",[combinator]:[validNonAnnotating],[kind==="items"?"unevaluatedItems":"unevaluatedProperties"]:false}; const data=kind==="items"?[1]:{x:1}; const result=jlib.compileSchema(schema).validate(data); console.log(JSON.stringify({combinator,kind,valid:result.valid,errors:result.errors.map(e=>e.code)}));}}'

Control output:

{"combinator":"anyOf","kind":"items","valid":false,"errors":["unevaluated-items-error"]}
{"combinator":"anyOf","kind":"properties","valid":false,"errors":["unevaluated-property-error"]}
{"combinator":"oneOf","kind":"items","valid":false,"errors":["unevaluated-items-error"]}
{"combinator":"oneOf","kind":"properties","valid":false,"errors":["unevaluated-property-error"]}

Expected behavior

The documented/source-grounded contract should hold without the drift described above.

Actual behavior

Failed alternatives suppress unevaluatedItems and unevaluatedProperties, causing valid-schema false acceptance.

Existing coverage

I checked the current issue and PR lists for overlapping titles/root-cause keywords before filing this. I did not find an item covering this same root cause.

Suggested fix

Allow only successful anyOf and oneOf branches to contribute evaluated-item and evaluated-property state.

Suggested tests

  • Add a regression test for the reproduction above.
  • Add a neighboring control assertion so the intended non-bug path remains covered.

Submitted with Codex.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions