diff --git a/docs/plugins/create-a-plugin.md b/docs/plugins/create-a-plugin.md index 7ba79017b0..0a8f14a8fe 100644 --- a/docs/plugins/create-a-plugin.md +++ b/docs/plugins/create-a-plugin.md @@ -116,8 +116,8 @@ Your plugin can capture incoming messages from Penpot using the { - // Validate the origin to ensure messages come from a trusted source - if (event.origin !== window.location.origin) { + // Validate the source to ensure messages come from the parent (Penpot) + if (event.source !== window.parent) { return; } // Handle the incoming message @@ -133,11 +133,11 @@ This setup allows for two-way communication between Penpot and your plugin. Penp ```js // Sending a message back to Penpot from your plugin -parent.postMessage(responseMessage, window.location.origin); +parent.postMessage(responseMessage, "*"); ``` -responseMessage is the data you want to send back to Penpot. --window.location.origin should be used as the target origin to ensure messages are only sent to the intended recipient. Never use'*' in production, as it allows any origin to receive the message. +- Using'*' as the target origin is acceptable here because the message content is controlled by your plugin (the sender), not by untrusted input. If you know the exact Penpot origin, you can use it instead for stricter security. ### Summary diff --git a/plugins/libs/plugins-runtime/src/lib/load-plugin.spec.ts b/plugins/libs/plugins-runtime/src/lib/load-plugin.spec.ts index 4621bdaa27..810ebd54b3 100644 --- a/plugins/libs/plugins-runtime/src/lib/load-plugin.spec.ts +++ b/plugins/libs/plugins-runtime/src/lib/load-plugin.spec.ts @@ -127,6 +127,7 @@ describe('plugin-loader', () => { sendMessage: vi.fn(), }, iframeWindow: mockIframeWindow, + manifest: { ...manifest, host: 'http://localhost:4202' }, } as unknown as Awaited>; vi.mocked(createPlugin).mockResolvedValue(mockPluginWithIframe); @@ -135,7 +136,7 @@ describe('plugin-loader', () => { const event = new MessageEvent('message', { data: 'test-message', - origin: window.location.origin, + origin: 'http://localhost:4202', }); Object.defineProperty(event, 'source', { value: mockIframeWindow }); window.dispatchEvent(event); @@ -145,15 +146,17 @@ describe('plugin-loader', () => { ); }); - it('should reject messages from unrecognized origins', async () => { + it('should reject messages from unrecognized sources', async () => { await loadPlugin(manifest); - window.dispatchEvent( - new MessageEvent('message', { - data: 'malicious-message', - origin: 'https://evil.com', - }), - ); + const event = new MessageEvent('message', { + data: 'malicious-message', + origin: 'https://evil.com', + }); + Object.defineProperty(event, 'source', { + value: { nodeType: 999 } as unknown as Window, + }); + window.dispatchEvent(event); expect(mockPluginApi.plugin.sendMessage).not.toHaveBeenCalled(); }); @@ -168,6 +171,7 @@ describe('plugin-loader', () => { sendMessage: vi.fn(), }, iframeWindow: mockIframeWindow1, + manifest: { ...manifest, host: 'http://localhost:4202' }, } as unknown as Awaited>; const mockPluginApi2 = { @@ -176,6 +180,7 @@ describe('plugin-loader', () => { sendMessage: vi.fn(), }, iframeWindow: mockIframeWindow2, + manifest: { ...manifest, host: 'http://localhost:4203' }, } as unknown as Awaited>; vi.mocked(createPlugin).mockResolvedValue(mockPluginApi1); @@ -186,12 +191,13 @@ describe('plugin-loader', () => { const event = new MessageEvent('message', { data: 'test', - origin: window.location.origin, + origin: 'http://localhost:4203', }); Object.defineProperty(event, 'source', { value: mockIframeWindow2 }); window.dispatchEvent(event); expect(mockPluginApi2.plugin.sendMessage).toHaveBeenCalledWith('test'); + expect(mockPluginApi1.plugin.sendMessage).not.toHaveBeenCalled(); }); it('should load plugin using ɵloadPlugin', async () => { diff --git a/plugins/libs/plugins-runtime/src/lib/load-plugin.ts b/plugins/libs/plugins-runtime/src/lib/load-plugin.ts index 00c153859a..d05178f1f7 100644 --- a/plugins/libs/plugins-runtime/src/lib/load-plugin.ts +++ b/plugins/libs/plugins-runtime/src/lib/load-plugin.ts @@ -29,10 +29,6 @@ const closeAllPlugins = () => { }; window.addEventListener('message', (event) => { - if (event.origin !== window.location.origin) { - return; - } - try { const senderPlugin = plugins.find((it) => it.iframeWindow === event.source);