ajv-validator / ajv-validator/ajv

discriminator rejects schemas whose tag values are Object.prototype member names (toString, constructor, ...)

Abierto
#2,650 0 comentarios 0 reacciones 0 asignados Ver en GitHub
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

Abrir la guía de contribución

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.