From 216d43a79d9c1d2544e822e3aee40dfa53194840 Mon Sep 17 00:00:00 2001 From: Pritesh Jain Date: Wed, 4 Nov 2026 08:54:52 +0530 Subject: [PATCH 1/3] fix(security): accept extensionEvent only from window.parent MKT-22139. post-robot.on without a window option accepts the message from any window and origin, so a sibling iframe could forge entrySave, entryChange and other host events. Co-Authored-By: Claude Opus 5.5 --- lib/extension.js | 3 ++- test/hostOnlyEventsSpec.js | 39 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 test/hostOnlyEventsSpec.js diff --git a/lib/extension.js b/lib/extension.js index 54d15fa7..12289828 100755 --- a/lib/extension.js +++ b/lib/extension.js @@ -79,7 +79,8 @@ class Extension { */ this.stack = new Stack(initializationData.data.stack, postRobot); - postRobot.on('extensionEvent', (event) => { + // Only the host (the window that embeds this iframe) may send events. + postRobot.on('extensionEvent', { window: window.parent }, (event) => { if (event.data.name === 'entrySave') { emitter.emitEvent('entrySave', [{ data: event.data.data }]); emitter.emitEvent('updateFields', [{ data: event.data.data }]); diff --git a/test/hostOnlyEventsSpec.js b/test/hostOnlyEventsSpec.js new file mode 100644 index 00000000..b13870ce --- /dev/null +++ b/test/hostOnlyEventsSpec.js @@ -0,0 +1,39 @@ +import testData from './data/testData.json'; +import Extension from '../lib/extension.js'; + +// Security bug: extensionEvent must be accepted only from the host window +// (window.parent), never from a sibling iframe or a popup. +// Karma runs specs in an iframe, so window.parent is replaced with a separate +// frame's window to prove the listener is scoped to whatever the parent is. +describe('Extension host-only events', () => { + let parentDescriptor; + let hostFrame; + let siblingFrame; + + const listeners = () => window.__postRobot__.requestListeners.extensionEvent; + + beforeEach(() => { + hostFrame = document.createElement('iframe'); + siblingFrame = document.createElement('iframe'); + document.body.appendChild(hostFrame); + document.body.appendChild(siblingFrame); + parentDescriptor = Object.getOwnPropertyDescriptor(window, 'parent'); + Object.defineProperty(window, 'parent', { configurable: true, get: () => hostFrame.contentWindow }); + }); + + afterEach(() => { + Object.defineProperty(window, 'parent', parentDescriptor); + hostFrame.remove(); + siblingFrame.remove(); + }); + + it('registers the extensionEvent listener for window.parent only', () => { + expect(window.parent).not.toBe(window); + // eslint-disable-next-line no-new + new Extension({ data: JSON.parse(JSON.stringify(testData)) }); + + expect(listeners().get(hostFrame.contentWindow)).toBeDefined(); + expect(listeners().get(siblingFrame.contentWindow)).toBeUndefined(); + expect(listeners().get(window.__postRobot__.WINDOW_WILDCARD)).toBeUndefined(); + }); +}); From f6d3fb56b600f3f82b6dba552e8e19e97e9b6aa4 Mon Sep 17 00:00:00 2001 From: Pritesh Jain Date: Wed, 4 Nov 2026 09:06:33 +0530 Subject: [PATCH 2/3] test: reset the extensionEvent listener before the host-only spec MKT-22139. So the spec fails on its assertion, not on post-robot's duplicate-listener error, when the fix is missing. Co-Authored-By: Claude Opus 5.5 --- test/hostOnlyEventsSpec.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/hostOnlyEventsSpec.js b/test/hostOnlyEventsSpec.js index b13870ce..e0a82e45 100644 --- a/test/hostOnlyEventsSpec.js +++ b/test/hostOnlyEventsSpec.js @@ -13,6 +13,8 @@ describe('Extension host-only events', () => { const listeners = () => window.__postRobot__.requestListeners.extensionEvent; beforeEach(() => { + // other specs build Extension too; post-robot allows one listener per window + delete window.__postRobot__.requestListeners.extensionEvent; hostFrame = document.createElement('iframe'); siblingFrame = document.createElement('iframe'); document.body.appendChild(hostFrame); From 48a4533669f50849bffe768d22883e33b0bed14b Mon Sep 17 00:00:00 2001 From: Pritesh Jain Date: Wed, 4 Nov 2026 09:11:01 +0530 Subject: [PATCH 3/3] test: host-only extensionEvent behaviour from host vs sibling frame MKT-22139. Delivers a post-robot request message from the (mocked) parent and from a sibling frame; only the parent's entrySave reaches onSave. Co-Authored-By: Claude Opus 5.5 --- test/hostOnlyEventsSpec.js | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/test/hostOnlyEventsSpec.js b/test/hostOnlyEventsSpec.js index e0a82e45..d9114ef1 100644 --- a/test/hostOnlyEventsSpec.js +++ b/test/hostOnlyEventsSpec.js @@ -38,4 +38,40 @@ describe('Extension host-only events', () => { expect(listeners().get(siblingFrame.contentWindow)).toBeUndefined(); expect(listeners().get(window.__postRobot__.WINDOW_WILDCARD)).toBeUndefined(); }); + + // Behaviour: a request-shaped message, as post-robot's own 'message' listener + // receives it, from the host is handled; the same message from a sibling is dropped. + let messageId = 0; + const deliver = (source, payload) => { + messageId += 1; + window.dispatchEvent(new MessageEvent('message', { + source, + origin: window.location.origin, + data: JSON.stringify({ + __postRobot__: { + type: 'postrobot_message_request', + id: `host-only-spec-${messageId}`, + hash: `host-only-spec-${messageId}`, + name: 'extensionEvent', + sourceDomain: window.location.origin, + fireAndForget: true, + data: payload + } + }) + })); + }; + + it('runs onSave for an entrySave from the host and ignores one from a sibling frame', (done) => { + const extension = new Extension({ data: JSON.parse(JSON.stringify(testData)) }); + const saved = []; + extension.entry.onSave((data) => saved.push(data)); + + deliver(siblingFrame.contentWindow, { name: 'entrySave', data: { from: 'sibling' } }); + deliver(hostFrame.contentWindow, { name: 'entrySave', data: { from: 'host' } }); + + setTimeout(() => { + expect(saved).toEqual([{ from: 'host' }]); + done(); + }, 100); + }); });