Chrome Extension Messages Leaking? postMessage targetOrigin Wildcard Exposes Data to Third-Party Iframes
Encountered this issue while developing a Chrome extension data collection tool for a client. Here's the root cause and solution.
TL;DR
window.postMessage(data, '*') broadcasts the message to all frames in the page, including third-party iframes. If your Chrome extension passes user data (like memberId, business reports) via postMessage, any embedded iframe can intercept it. Change '*' to window.location.origin to restrict the recipient precisely.
The Problem
A Chrome extension's injected script passes e-commerce data to its content script via postMessage:
// ❌ Unsafe: message broadcasts to ALL frames
window.postMessage({
type: 'CCL_SHOP_REPORT_DAILY',
memberId: 'b2b-2214126315258ad300', // user ID
rows: [{ uv: 403, payAmt: 19478.47 }] // business data
});
// Equivalent to window.postMessage(data, '*')
If the page embeds third-party iframes (ads, analytics, social widgets), their message event listeners will also receive this message.
Root Cause
The second argument to postMessage, targetOrigin, determines the message's delivery scope:
| targetOrigin | Behavior |
|---|---|
'*' or omitted | Broadcasts to all frames, no origin check |
'https://example.com' | Only delivers to frames with origin https://example.com |
window.location.origin | Only delivers to frames same-origin as the current page |
When omitted, the browser defaults to '*'. This is especially dangerous in Chrome extension scenarios — injected scripts run on e-commerce platform pages that may contain multiple third-party iframes.
Solution
Wrap postMessage in a Safe Helper
// safePostMessage: enforce window.location.origin
function safePostMessage(data) {
window.postMessage(data, window.location.origin);
}
// Usage
safePostMessage({
type: 'CCL_SHOP_REPORT_DAILY',
subType: 'daily',
memberId: memberId,
rows: [row]
});
Validate Origin on the Receiving End Too
// Content script message listener
window.addEventListener('message', (event) => {
// ✅ Verify origin
if (event.origin !== window.location.origin) return;
// ✅ Validate message structure
if (!event.data || typeof event.data.type !== 'string') return;
switch (event.data.type) {
case 'CCL_SHOP_REPORT_DAILY':
handleDailyReport(event.data);
break;
case 'CCL_ITEM_WEEKLY_REPORT':
handleWeeklyReport(event.data);
break;
}
});
When is '*' Acceptable?
Only when the message contains zero sensitive information and the recipient's origin is unpredictable — e.g., a pure UI state notification like "panel opened". Even then, window.location.origin is safer.
Caveats
Caveats
- Chrome extension MAIN world scripts and content scripts run in separate JavaScript isolation contexts —
postMessageis their standard communication channel. Protect it carefully (if you encounter duplicate message processing after hot reload, you'll also need to manually clean up old listeners). - Receiver-side
event.originvalidation and sender-sidetargetOriginrestriction are both required. One-sided protection is incomplete. - If messages need to cross origins (e.g., from page to extension background), use
chrome.runtime.sendMessage(see Service Worker Token Sync) instead ofpostMessage.