diff --git a/shell/renderer/api/electron_api_context_bridge.cc b/shell/renderer/api/electron_api_context_bridge.cc index 78a5aae183..f178186f6a 100644 --- a/shell/renderer/api/electron_api_context_bridge.cc +++ b/shell/renderer/api/electron_api_context_bridge.cc @@ -725,7 +725,18 @@ v8::MaybeLocal CreateProxyForAPI( { v8::Context::Scope inner_destination_context_scope( destination_context); - proxy.Set(key, passed_value.ToLocalChecked()); + // Use CreateDataProperty (not Set) so that a key named "__proto__" + // becomes an own data property instead of invoking the inherited + // Object.prototype.__proto__ setter and mutating the prototype. + v8::Local proxied_value = passed_value.ToLocalChecked(); + if (key->IsName()) { + std::ignore = proxy.GetHandle()->CreateDataProperty( + destination_context, key.As(), proxied_value); + } else { + std::ignore = proxy.GetHandle()->CreateDataProperty( + destination_context, key.As()->Value(), + proxied_value); + } } } } diff --git a/spec/api-context-bridge-spec.ts b/spec/api-context-bridge-spec.ts index 1f8c043be6..e5700f1b99 100644 --- a/spec/api-context-bridge-spec.ts +++ b/spec/api-context-bridge-spec.ts @@ -483,6 +483,52 @@ describe('contextBridge', () => { expect(result).to.deep.equal([123, 456, 789, false]); }); + it('should not mutate the prototype when an object with an own __proto__ key is sent over the bridge', async () => { + await makeBindingWindow(() => { + contextBridge.exposeInMainWorld('example', { + receive: (obj: any) => { + return [ + Object.getPrototypeOf(obj) === Object.prototype, + obj.polluted, + Object.prototype.hasOwnProperty.call(obj, '__proto__'), + obj.data + ]; + } + }); + }); + const result = await callWithBindings((root: any) => { + const payload = Object.defineProperty({ data: 1 }, '__proto__', { + value: { polluted: true }, + enumerable: true, + writable: true, + configurable: true + }); + return root.example.receive(payload); + }); + expect(result).to.deep.equal([true, undefined, true, 1]); + }); + + it('should not mutate the prototype when an object with an own __proto__ key is exposed', async () => { + await makeBindingWindow(() => { + const payload = Object.defineProperty({ data: 1 }, '__proto__', { + value: { polluted: true }, + enumerable: true, + writable: true, + configurable: true + }); + contextBridge.exposeInMainWorld('example', payload); + }); + const result = await callWithBindings((root: any) => { + return [ + Object.getPrototypeOf(root.example) === Object.prototype, + root.example.polluted, + Object.prototype.hasOwnProperty.call(root.example, '__proto__'), + root.example.data + ]; + }); + expect(result).to.deep.equal([true, undefined, true, 1]); + }); + it('it should proxy null', async () => { await makeBindingWindow(() => { contextBridge.exposeInMainWorld('example', null);