From 23a6efb714dec80e2cf45d3054d18d701162e4dd Mon Sep 17 00:00:00 2001 From: "trop[bot]" <37223003+trop[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2026 10:57:32 -0700 Subject: [PATCH] fix: use CreateDataProperty when copying objects across contextBridge (#51085) Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com> Co-authored-by: Sam Attard --- .../api/electron_api_context_bridge.cc | 13 +++++- spec/api-context-bridge-spec.ts | 46 +++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) 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);