RoastMyOpsec

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.'

PipeBrowser gateMust checkTakeaway
fetch / XHRCORSACAO allowlist + authSee wildcard vs allowlist
postMessageNone like CORStargetOrigin + event.originNever * with secrets
WebSocketNo CORS on framesOrigin on upgradeSee WebSockets vs CORS
iframe sandboxChild capabilitiesFlags on the tagDoes 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 now

FAQ

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