Skip to content

Commit 172ecec

Browse files
committed
fix: prevent proto on renames
(cherry picked from commit 162f367)
1 parent 30766a2 commit 172ecec

3 files changed

Lines changed: 45 additions & 1 deletion

File tree

‎API.md‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2550,7 +2550,7 @@ const value = await Joi.compile(schema).validateAsync(input);
25502550
// value === { x123x: 'x', x1x: 'y', x0x: 'z', x4x: 'test' }
25512551
```
25522552

2553-
Possible validation errors: [`object.rename.multiple`](#objectrenamemultiple), [`object.rename.override`](#objectrenameoverride)
2553+
Possible validation errors: [`object.rename.multiple`](#objectrenamemultiple), [`object.rename.override`](#objectrenameoverride), [`object.rename.proto`](#objectrenameproto)
25542554

25552555
#### `object.schema([type])`
25562556

@@ -4145,6 +4145,19 @@ Additional local context properties:
41454145
}
41464146
```
41474147

4148+
#### `object.rename.proto`
4149+
4150+
The target property is `__proto__`, which would set the object prototype instead of creating a key.
4151+
4152+
Additional local context properties:
4153+
```ts
4154+
{
4155+
from: string, // Origin property name of the rename
4156+
to: string, // Target property of the rename
4157+
pattern: boolean // Indicates if the rename source was a pattern (regular expression)
4158+
}
4159+
```
4160+
41484161
#### `object.schema`
41494162

41504163
The object was not a **joi** schema.

‎lib/types/keys.js‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,7 @@ module.exports = Any.extend({
547547
'object.regex': '{{#label}} must be a RegExp object',
548548
'object.rename.multiple': '{{#label}} cannot rename {{:#from}} because multiple renames are disabled and another key was already renamed to {{:#to}}',
549549
'object.rename.override': '{{#label}} cannot rename {{:#from}} because override is disabled and target {{:#to}} exists',
550+
'object.rename.proto': '{{#label}} cannot rename {{:#from}} because target {{:#to}} is a reserved key',
550551
'object.schema': '{{#label}} must be a Joi schema of {{#type}} type',
551552
'object.unknown': '{{#label}} is not allowed',
552553
'object.with': '{{:#mainWithLabel}} missing required peer {{:#peerWithLabel}}',
@@ -874,6 +875,15 @@ internals.rename = function (schema, value, state, prefs, errors) {
874875
}
875876
}
876877

878+
if (to === '__proto__') {
879+
errors.push(schema.$_createError('object.rename.proto', value, { from, to, pattern }, state, prefs));
880+
if (prefs.abortEarly) {
881+
return false;
882+
}
883+
884+
continue;
885+
}
886+
877887
if (value[from] === undefined) {
878888
delete value[to];
879889
}

‎test/types/object.js‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3053,6 +3053,27 @@ describe('object', () => {
30533053
Helper.validate(Joi.compile(schema), [[{ other: 'here', A: 100, c: 50 }, true, { other: 'here', A: 100, b: 100, c: 50 }]]);
30543054
});
30553055

3056+
it('errors on a template target rendering __proto__ instead of setting the prototype', () => {
3057+
3058+
const payload = JSON.parse('{"user":"alice","x-__proto__":{"isAdmin":true,"role":"superuser"}}');
3059+
3060+
const schema = Joi.object({ user: Joi.string().required() })
3061+
.rename(/^x-(.+)$/, Joi.x('{#1}'), { multiple: true })
3062+
.unknown(false);
3063+
3064+
const { value, error } = schema.validate(payload);
3065+
expect(error).to.be.an.error('"value" cannot rename "x-__proto__" because target "__proto__" is a reserved key');
3066+
expect(error.details[0].type).to.equal('object.rename.proto');
3067+
expect(Object.getPrototypeOf(value)).to.equal(Object.prototype);
3068+
expect(value.isAdmin).to.not.exist();
3069+
expect(Object.prototype.isAdmin).to.not.exist();
3070+
3071+
const { value: value2, error: error2 } = schema.validate(payload, { abortEarly: false });
3072+
expect(error2.details[0].type).to.equal('object.rename.proto');
3073+
expect(Object.getPrototypeOf(value2)).to.equal(Object.prototype);
3074+
expect(value2.isAdmin).to.not.exist();
3075+
});
3076+
30563077
it('uses template', () => {
30573078

30583079
const schema = Joi.object()

0 commit comments

Comments
 (0)