diff --git a/src/web-ui/src/tools/generative-widget/GenerativeWidgetFrame.test.tsx b/src/web-ui/src/tools/generative-widget/GenerativeWidgetFrame.test.tsx index 04da33f4ec..3584d2bf81 100644 --- a/src/web-ui/src/tools/generative-widget/GenerativeWidgetFrame.test.tsx +++ b/src/web-ui/src/tools/generative-widget/GenerativeWidgetFrame.test.tsx @@ -63,4 +63,21 @@ describe('GenerativeWidgetFrame shell', () => { expect(iframe.getAttribute('sandbox')).toContain('allow-same-origin'); expect(iframe.contentDocument?.documentElement.outerHTML).toContain('bitfun-widget'); }); + + it('rejects non-parent-window message sources before processing widget updates (anti-injection)', () => { + // The widget shell listens for `bitfun-widget:update` posted by its host page. + // Any other window able to postMessage into the host could forge that event + // and have arbitrary HTML applied via setContent, so the source gate must + // reject non-parent windows before any data processing (including the + // setContent call) happens. + const shellSource = GENERATIVE_WIDGET_SHELL_HTML; + const listenerStart = shellSource.indexOf("window.addEventListener('message'"); + const setContentCall = shellSource.indexOf('setContent(', listenerStart); + + expect(listenerStart).toBeGreaterThan(-1); + expect(setContentCall).toBeGreaterThan(listenerStart); + + const listenerBody = shellSource.slice(listenerStart, setContentCall); + expect(listenerBody).toMatch(/if \(event\.source !== window\.parent\) return;/); + }); }); diff --git a/src/web-ui/src/tools/generative-widget/GenerativeWidgetFrame.tsx b/src/web-ui/src/tools/generative-widget/GenerativeWidgetFrame.tsx index 30c0839cad..8395ffc95c 100644 --- a/src/web-ui/src/tools/generative-widget/GenerativeWidgetFrame.tsx +++ b/src/web-ui/src/tools/generative-widget/GenerativeWidgetFrame.tsx @@ -860,6 +860,7 @@ ${createWidgetAppearanceStaticShellCss()} }, true); window.addEventListener('message', function (event) { + if (event.source !== window.parent) return; var data = event.data; if (!data) return; if (data.type === 'bitfun-widget:clear-selection') {