ajv-validator / ajv-validator/ajv
discriminator rejects schemas whose tag values are Object.prototype member names (toString, constructor, ...)
- Lenguaje dominante
- TypeScript
- Estrellas
- 14.8k
- Forks
- 1k
- Métricas de merge de PR
- Sin PR fusionados en 30 d
Descripción
>
> (`.github/ISSUE_TEMPLATE/bug-or-error-report.md`, label `bug report`). Plain correctness bug,
>
> `prototype constructor`. No matching reports found (re-checked 2026-07-20).
---
**Title:** `discriminator` rejects schemas whose tag values are `Object.prototype` member names (`toString`, `constructor`, ...)
**What version of Ajv are you using? Does the issue happen if you use the latest version?**
8.20.0, reproduced on HEAD `f177fe32`. The check is unchanged on `master`.
**Ajv options object**
```javascript
const options = {discriminator: true}
```
**JSON Schema**
Two distinct branches. The discriminator tag values are unique, so the schema is valid, yet
compilation throws because one value happens to be an `Object.prototype` member name:
```json
{
"type": "object",
"discriminator": {"propertyName": "kind"},
"required": ["kind"],
"oneOf": [
{"properties": {"kind": {"const": "toString"}}, "required": ["a"]},
{"properties": {"kind": {"const": "foo"}}, "required": ["b"]}
]
}
```
**Sample data**
```json
{"kind": "toString", "a": 1}
```
**Your code**
```javascript
const Ajv = require("ajv")
const ajv = new Ajv(options)
const validate = ajv.compile(schema) // throws before any validation
console.log(validate(data), validate.errors)
```
**Validation result, data AFTER validation, error messages**
```
Error: discriminator: "kind" values must be unique strings
at addMapping (.../lib/vocabularies/discriminator/index.ts:104)
```
The schema never compiles. A scan over tag-value pairs:
```
[foo,bar] -> OK, validate({kind:'foo',a:1}) = true
[toString,foo] -> ERR: discriminator: "kind" values must be unique strings
[constructor,x] -> ERR: discriminator: "kind" values must be unique strings
[hasOwnProperty,y] -> ERR: discriminator: "kind" values must be unique strings
[valueOf,z] -> ERR: discriminator: "kind" values must be unique strings
[__proto__,w] -> ERR: discriminator: "kind" values must be unique strings
[dup,dup] -> ERR (correct: genuine duplicate)
```
Ajv rejects each `Object.prototype` member name as a "duplicate" even when it occurs once
across all branches.
**What results did you expect?**
The schema compiles and `validate({kind: "toString", a: 1})` returns `true`. `"toString"` is a
legal tag value and differs from `"foo"`, so no duplicate exists. Genuine duplicates (two
branches both using `const: "dog"`) must still throw, and they do; a fix has to preserve that.
**Root cause**
`lib/vocabularies/discriminator/index.ts`. The mapping accumulator is a plain object literal
(line 64) and the uniqueness guard uses the `in` operator (line 104):
```javascript
const oneOfMapping: {[T in string]?: number} = {} // line 64
...
function addMapping(tagValue, i) {
if (typeof tagValue != "string" || tagValue in oneOfMapping) { // line 104
throw new Error(`discriminator: "${tagName}" values must be unique strings`)
}
oneOfMapping[tagValue] = i
}
```
`in` walks the prototype chain: `"toString" in {}` is `true` for an empty map, as is
`"constructor" in {}`, `"hasOwnProperty" in {}`, `"valueOf" in {}` and `"__proto__" in {}`. The
first branch using such a value trips the guard against an inherited member.
**Suggested fix**
Make the accumulator prototype-free:
```diff
- const oneOfMapping: {[T in string]?: number} = {}
+ const oneOfMapping: {[T in string]?: number} = Object.create(null)
```
or keep the literal and test own membership:
```diff
- if (typeof tagValue != "string" || tagValue in oneOfMapping) {
+ if (typeof tagValue != "string" || Object.prototype.hasOwnProperty.call(oneOfMapping, tagValue)) {
```
`Object.create(null)` is the smaller change and also covers the assignment on line 107. A
regression test should compile a discriminator with `const: "toString"` / `const: "constructor"`
branches and confirm a real duplicate still throws.
**Are you going to resolve the issue?**
Yes. I can send the one-line `Object.create(null)` patch plus a test; flagging first in case you
prefer the `hasOwnProperty` form.
---
*Found via property-based & differential bug-hunting, part of an effort to scale PBT (DepTyCheck-based) testing across the OSS ecosystem.*
*If this is intended / by-design: I'm really sorry, please just close it — no need to flag or ban me. I'm trying to scale property-based testing across the whole ecosystem and my publishing agents may have gotten this one wrong. I read every issue and follow up on each.*
Guía de contribución
Evaluación
Este issue todavía no se ha evaluado.