diff --git a/lib/types/keys.js b/lib/types/keys.js index 2680bbb2..a920afa4 100755 --- a/lib/types/keys.js +++ b/lib/types/keys.js @@ -675,8 +675,15 @@ internals.clone = function (value, prefs) { return Clone(value, { shallow: true }); } - const clone = Object.create(Object.getPrototypeOf(value)); + const proto = Object.getPrototypeOf(value); + const clone = Object.create(proto); Object.assign(clone, value); + + // Restore the prototype in case of pre-existing prototype pollution + if (Object.getPrototypeOf(clone) !== proto) { + Object.setPrototypeOf(clone, proto); + } + return clone; } diff --git a/test/types/object.js b/test/types/object.js index bb909273..88ae3bd6 100755 --- a/test/types/object.js +++ b/test/types/object.js @@ -105,6 +105,28 @@ describe('object', () => { expect(schema.validate(new Test()).value).to.be.instanceof(Test); }); + it('does not pollute the cloned value prototype via a __proto__ key', () => { + + const schema = Joi.object({ name: Joi.string() }); + + const payload = JSON.parse('{"name":"alice","__proto__":{"isAdmin":true,"role":"superuser"}}'); + expect(Object.getPrototypeOf(payload)).to.equal(Object.prototype); + expect(payload.isAdmin).to.not.exist(); + + const { value, error } = schema.validate(payload); + expect(error).to.not.exist(); + expect(Object.getPrototypeOf(value)).to.equal(Object.prototype); + expect(value.isAdmin).to.not.exist(); + expect(value.role).to.not.exist(); + + // Same with allowUnknown disabled + + const { value: value2, error: error2 } = schema.validate(payload, { allowUnknown: false }); + expect(error2).to.not.exist(); + expect(Object.getPrototypeOf(value2)).to.equal(Object.prototype); + expect(value2.isAdmin).to.not.exist(); + }); + it('allows any key when schema is undefined', () => { Helper.validate(Joi.object(), [[{ a: 4 }, true]]);