Vulnerabilities
postMessage vs CORS for Embedded Widgets
March 21, 2025
postMessage vs CORS for embedded widgets is two different cross-origin pipes. CORS decides whether fetch may read another origin's HTTP response. window.postMessage sends a structured clone to another window; CORS headers do not filter it. The OPSEC is targetOrigin (never *) and checking event.origin on the receiver before trusting the data. Tokens, session flags, and PII in a * postMessage are a publish. iframe sandbox and CSP frame-ancestors still matter for who may load whom. RoastMyOpsec does not execute postMessage, does not inspect widget JS for targetOrigin, and does not score missing origin checks. It may read ACAO on HTTP APIs in app mode.
The practical difference
A checkout iframe that fetch()es your API needs CORS. The same iframe that parent.postMessage({token}) needs targetOrigin: 'https://pay.example' and a receiver that ignores other origins. Mixing them up is how teams 'fix CORS' and still broadcast the session to every opener.
WebSockets are a third pipe — Origin on upgrade, not postMessage. See WebSockets vs CORS.
How to choose a pipe
Same-origin widgets: skip postMessage; call functions. Cross-origin embeds: postMessage with an exact origin string (scheme-host-port), validate origin on both sides, keep payloads boring (no cookies, no raw cards). Server APIs: CORS allowlist, not postMessage. Do not use * because 'the CDN hostname changes.'
| Pipe | Browser gate | Must check | Takeaway |
|---|---|---|---|
| fetch / XHR | CORS | ACAO allowlist + auth | See wildcard vs allowlist |
| postMessage | None like CORS | targetOrigin + event.origin | Never * with secrets |
| WebSocket | No CORS on frames | Origin on upgrade | See WebSockets vs CORS |
| iframe sandbox | Child capabilities | Flags on the tag | Does not replace origin checks |
What the roast can prove
HTTP CORS behavior can surface. Widget postMessage is not scored. Hunt targetOrigin: '*' in your own bundle. Pair with iframe sandbox vs frame-ancestors if the embed is third-party HTML.
Common mistakes
The first mistake is targetOrigin: '*' 'just for staging' left in production.
The second mistake is trusting event.data without checking event.origin.
The third mistake is putting a session JWT in the message because CORS blocked the cookie.
Free audit the URL you own
RoastMyOpsec is a defensive public-surface roast: headers, cookies, sensitive paths, and more — no exploit payloads. Start with the free audit, then open the vault if the blurred findings look expensive.
Free audit nowFAQ
- Does CORS stop postMessage?
- No. They are different APIs. Lock targetOrigin and event.origin even when CORS is tight.
- Is postMessage * ever acceptable?
- Only for non-sensitive, non-auth payloads you would print on the homepage. Prefer an explicit origin anyway.
- Does RoastMyOpsec inspect postMessage?
- No. It does not run widget scripts or score targetOrigin.
Sources
Related guides
Vulnerabilities
CORS Wildcard vs Allowlist for Public APIsVulnerabilities
WebSockets vs CORS for Browser APIsSafety
iframe sandbox vs CSP frame-ancestors