Join our Newsletter — 33% off our NHI Course
Home› Guides› postMessage and Cross-Window Messaging Security Guide: Origin Checks…
Guide Web & API Security

postMessage and Cross-Window Messaging Security Guide: Origin Checks Done Right

← All guides
By NHI Mgmt Group, edited by Lalit Choda Updated 27 October 2026 13 min read
On this page

The browser's postMessage API deliberately lets windows from different origins talk to each other, so every message listener is a cross-origin entry point that must decide for itself who it trusts. Chat widgets, payment frames, embedded editors, analytics tags and single sign-on popups all use it, and the defects are remarkably consistent: listeners that never check event.origin, origin checks written with substring matching, senders that broadcast tokens with a "*" target, and message data written straight into the DOM. This guide explains how cross-window messaging works, how to validate origin, source and payload properly, how to design widget and SSO popup protocols, how sandboxing, MessageChannel and Cross-Origin-Opener-Policy change the picture, and how to find weak listeners. It is for front-end developers, identity engineers building popup sign-in flows, and reviewers testing third-party embeds, and it goes deeper on one topic from the Client-Side Security Guide.

Key takeaways

  • No listener is the safest listener. MDN's guidance is blunt: if you do not expect messages from other sites, do not add message event listeners at all.
  • Check origin by exact match. Compare event.origin against a fixed allowlist of full origins (scheme, host and port); indexOf, includes, endsWith and loose regular expressions all let attacker domains through.
  • A trusted origin does not mean trusted data. Validate the message schema too, because an XSS flaw on the trusted sender lets an attacker send any message.
  • Never send secrets to "*". A sender must name the exact targetOrigin, because the target window can be navigated to an attacker page without the sender knowing.
  • Bind messages to a specific window and session. Check event.source, use a per-flow nonce, and move to a private MessageChannel after the handshake.

How cross-window messaging works

A script with a reference to another window (an iframe's contentWindow, window.parent, window.opener or the return value of window.open()) calls otherWindow.postMessage(message, targetOrigin, transfer) or the options form postMessage(message, { targetOrigin, transfer }). The browser serialises the message with the structured clone algorithm and delivers a MessageEvent to the target only if the target document's origin matches targetOrigin exactly, including scheme, host and port. The receiver sees three security-relevant properties.

Property or argumentMeaningSecurity note
targetOrigin (sender)The origin the recipient must have; defaults to "/" (the sender's own origin); "*" means anyThe only control the sender has over who reads the message
event.origin (receiver)Origin of the sending window when postMessage was calledSet by the browser, cannot be forged by page script; MDN notes it is not guaranteed to be the window's current or future origin
event.source (receiver)Reference to the sending windowLets the receiver confirm the message came from the specific frame or popup it opened, and reply to it
event.data (receiver)The cloned messageAttacker-controlled unless origin and schema checks pass
event.ports (receiver)Transferred MessagePort objectsUsed to set up a private channel

Any window able to obtain a reference can post to any other. MDN points out that any window, including a malicious one, can send a message to any frame in the iframe hierarchy beneath the current document, and an attacker page can open your page in a popup or frame it (where framing is allowed) to get that reference.

Related messaging channels

APIScopeMain risk
window.postMessageCross-origin, between window referencesMissing origin checks, wildcard targets
MessageChannel / MessagePortPoint-to-point between two contexts after a port is transferredThe initial port handoff still needs a correct targetOrigin
BroadcastChannelSame-origin contexts (tabs, frames, workers)Any same-origin page, including one with an XSS flaw, can read and send
Worker postMessageBetween a page and its workerTreat worker results as untrusted if the worker processes external data
Browser extension messagingBetween page and extension content scriptsPages can send messages that extensions trust too readily

Validating the sender: origin and source

MDN states that failure to check the origin and possibly source properties enables cross-site scripting. ASVS 5.0 requirement V3.5.5 requires that messages received by the postMessage interface are discarded if the origin is not trusted or the syntax is invalid.

CheckVerdictWhy
No checkVulnerableAny site can drive the listener
event.origin.indexOf("example.com") !== -1VulnerableMatches https://example.com.attacker.net; OWASP's HTML5 cheat sheet gives this exact pattern as unsafe
event.origin.endsWith("example.com")VulnerableMatches https://evilexample.com
/https:\/\/.*\.example\.com/VulnerableUnanchored, and the unescaped dot and wildcard match far more than intended
event.origin.startsWith("https://example.com")VulnerableMatches https://example.com.attacker.net
ALLOWED.has(event.origin) with a Set of full originsCorrectExact string comparison of scheme, host and port
Exact origin plus event.source === expectedWindowBestAlso rejects other frames or tabs from the same trusted origin
  • Write the allowlist as full origins: https://pay.example.com, not a host name. Include the port only where it is non-default, and never include http: variants in production.
  • Handle the opaque origin: sandboxed iframes without allow-same-origin and data: documents have an opaque origin, which arrives as the string "null". Never put "null" on an allowlist, because any attacker can create a sandboxed frame that sends it.
  • Check source when you know the window: a parent that created an iframe should compare event.source with iframe.contentWindow; an opener should compare with the popup reference it holds.
  • Check before any work: return immediately on failure, before parsing, logging large payloads or changing state.
  • Subdomain trust is a decision: accepting every subdomain means any subdomain takeover or user-content host can talk to the listener. List them explicitly.

Validating the message: schema and safe handling

Origin checks establish who sent a message, not whether it is safe. MDN warns that a security hole in a trusted sender could otherwise open a cross-site scripting hole in your site, and OWASP adds that an XSS flaw on the sending page lets an attacker send messages in any format.

  • Define a protocol: each message carries a type from a fixed set, a protocol version and only the fields that type allows. Reject unknown types, extra properties and wrong data types.
  • Treat data as data: never pass message content to eval(), new Function, setTimeout with a string, innerHTML or document.write; write text with textContent. ASVS 5.0 V3.2.2 and V1.3.2 cover safe rendering and dynamic code. See the Trusted Types and DOM XSS Prevention Guide.
  • Validate URLs in messages: a message that says "navigate to" or "load this script" is an open redirect or script injection unless the URL is parsed with new URL() and checked against allowed schemes and hosts. See the Open Redirect Guide.
  • Do not dispatch on message content: patterns such as window[event.data.fn](event.data.args) let the sender call any global function. Map types to handlers with a fixed table.
  • Beware of prototype keys: merging event.data into configuration objects can pollute prototypes. See the Prototype Pollution Guide.
  • Bound size and rate: ignore oversized messages and throttle bursts so a hostile frame cannot freeze the page.

Sending safely: targetOrigin and what to send

  • Always set an exact targetOrigin: MDN warns that a malicious site can change the location of the window without your knowledge and intercept data sent with "*". For replies, use event.source.postMessage(reply, event.origin) only after event.origin passed the allowlist.
  • Know the exceptions: MDN notes that sending to a data: or file: context currently requires "*". That is a reason to avoid such targets for anything sensitive, not a reason to use "*" elsewhere.
  • Send the minimum: an embedded frame rarely needs a full access token. Send a short-lived, audience-restricted value or an authorisation code that the receiving server exchanges.
  • Do not broadcast to all frames: loops that post the same message to every window.frames[i] leak to any third-party frame on the page.
  • Re-check before sending later: if a reply is sent after asynchronous work, the target may have navigated; the exact targetOrigin is what makes that safe.

Widget, payment and SSO popup patterns

ScenarioTypical flawSecure design
Embedded widget (chat, support, analytics) talking to its host pageWidget accepts commands from any parent; host page accepts any child messageWidget reads its allowed host origins from server configuration per customer; both sides check origin and source; commands limited to a small typed set
Payment or card-entry iframeParent sends configuration with "*"; frame posts results to "*"Frame posts only to the merchant origin registered for the session; card data never leaves the frame; the server confirms payment status rather than trusting the message
SSO or OAuth popup returning a result to the openerPopup posts tokens to window.opener with "*"; opener accepts results from any originPopup posts to the registered client origin only; result carries the state or nonce the opener generated; the opener verifies origin, source === popup and the nonce; prefer returning an authorisation code that the backend exchanges
Silent token renewal in a hidden iframeListener on every page accepts tokensListener installed only for the renewal, removed after one valid message or a timeout
Micro-frontends on different subdomainsWildcard subdomain trustExplicit origin list per frontend and a versioned message schema

For identity flows, the nonce matters as much as the origin: without it, an attacker page that opens the popup itself can receive or inject a result. See the OAuth for Browser-Based Applications and the BFF Pattern Guide and the Identity Provider and SSO Security Guide.

Isolation tools: sandbox, MessageChannel and COOP

  • Sandboxed iframes: the sandbox attribute treats framed content as a unique origin unless allow-same-origin is set, and blocks scripts unless allow-scripts is set. MDN strongly discourages combining allow-scripts and allow-same-origin for same-origin content, because the frame can then remove its own sandbox. Serve untrusted content from a separate origin.
  • MessageChannel after a handshake: the parent creates a MessageChannel, transfers port2 to the verified frame with an exact targetOrigin, and from then on both sides use their ports. Other windows cannot post into that channel, so the global message listener can be removed after the handshake.
  • Cross-Origin-Opener-Policy: same-origin places the document in its own browsing context group and severs window.opener references to cross-origin popups and openers, which stops attacker pages scripting or messaging your window through those references. same-origin-allow-popups keeps references to popups you open, which suits a relying party that opens an identity provider popup. ASVS 5.0 V3.4.8 asks for COOP on document responses. See the Permissions Policy, COOP, COEP and CORP Guide.
  • Framing controls: CSP frame-ancestors limits who can embed your page and so who can get a contentWindow reference to it. See the Clickjacking Guide.

Finding weak listeners and measuring progress

  1. Inventory: search first-party code for addEventListener("message", onmessage, postMessage( and "*" targets; in the browser, developer tools list the event listeners registered on window, including those added by third-party scripts.
  2. Classify: for each listener record the expected senders, the message types handled and what each type can change (DOM, navigation, storage, tokens).
  3. Review: confirm exact origin checks, source checks where possible, schema validation, and no dangerous sinks. Static analysis rules for missing origin checks help at scale. See the Secure Code Review Guide.
  4. Test: in an authorised test, host a page on an unlisted origin and on look-alike origins, frame or open the target, and confirm every message is ignored; repeat from a sandboxed frame to confirm "null" is rejected.
  5. Fix and roll out: ship a shared listener helper that enforces the allowlist and schema, migrate listeners to it, and add a lint rule that bans raw listeners and "*" targets. Roll back per feature flag if a legitimate partner origin was missed.

Metrics:

  • Message listeners without an exact origin allowlist (target zero).
  • postMessage calls using "*" with non-public data (target zero).
  • Share of listeners using the shared validation helper.
  • Third-party scripts that register message listeners, reviewed at onboarding. See the Third-Party JavaScript Management Guide.
  • Rejected-message counts from client telemetry, which show probing or misconfigured partners.

Common failure patterns

  • The missing check: a listener added "temporarily" for a partner integration trusts every message and writes event.data.html into the page.
  • Substring origin checks: origin.includes("example.com") accepts example.com.attacker.net, and the review passes because a check exists.
  • Tokens posted to "*": an SSO popup or embedded login frame sends an access token to whatever window opened it, so an attacker page that opens the popup receives the token.
  • Trusting "null": a listener allows "null" to support a sandboxed preview frame, which any attacker can also create.
  • Navigation by message: a widget accepts {type: "redirect", url} and assigns it to location.href, enabling javascript: URLs and phishing redirects.
  • Listener that never goes away: a one-time handshake listener stays registered for the page's lifetime and accepts replayed results.

Framework mapping

ControlOWASP ASVS 5.0NIST SP 800-53 Rev. 5NIST CSF 2.0
Discard messages from untrusted origins or with invalid syntaxV3.5.5SI-10, AC-4PR.PS-06
Safe rendering of message dataV3.2.2, V1.3.2SI-10PR.PS-06
Opener isolation with COOPV3.4.8SC-7PR.IR-01
Minimal, audience-bound tokens in messagesV14.2.1SC-8, IA-5PR.DS-02

For threat modelling, ATT&CK T1059.007 JavaScript describes the script execution an injectable listener enables, and T1539 Steal Web Session Cookie the session theft that follows. ISO/IEC 27001:2022 Annex A control A.8.28 Secure coding covers the coding rules.

Where identity fits

Cross-window messages often carry identity material: ID tokens and access tokens from SSO popups, session handoff codes between a portal and an embedded app, and API keys that a host page passes to a third-party widget so it can call the vendor's backend. Each of those is a credential crossing an origin boundary. Prefer designs where the browser only ever carries a one-time code bound to the client and the nonce, exchanged server side for a token; where a widget needs a vendor API key, issue a publishable key scoped to that customer origin rather than a secret key, and rotate it if it was ever posted to "*". See the API Key Management Guide.

Practitioner checklist

  • Every message listener is inventoried with its expected senders and message types, and unneeded listeners are removed.
  • Origins are checked by exact match against a Set of full origins; no substring, suffix or unanchored regex checks remain.
  • "null" is never an allowed origin.
  • event.source is compared with the expected frame or popup wherever the receiver holds a reference.
  • Every message is validated against a typed schema and handled through a fixed type-to-handler table.
  • Message data never reaches eval, innerHTML, navigation or script URLs without validation.
  • Senders always use an exact targetOrigin; no sensitive data is posted to "*".
  • SSO and payment popups bind results to a per-flow nonce and prefer codes over tokens.
  • Long-lived conversations move to a MessageChannel after a verified handshake.
  • COOP, frame-ancestors and sandboxed iframes on a separate origin limit who can obtain window references.

Standards and references

Related NHI Mgmt Group resources: Client-Side Security Guide · Permissions Policy, COOP, COEP and CORP Guide · Trusted Types and DOM XSS Prevention Guide · TypeScript and JavaScript Secure Coding Guide

Free weekly newsletter

Subscribe to the NHI & AI Identity Journal

The latest on NHI and Agentic AI security – articles, research, breaches, news and events every week.

Bonus 33% off our NHI Course when you subscribe.

NHIMG Editorial Note
Researched and written by the NHI Mgmt Group editorial team using AI tools, under the editorial direction of Lalit Choda. Facts are drawn from the sources listed on this page and checked by our automated quality process. Last updated 27 October 2026. Spotted an error? Use our contact page.
NHI Mgmt Group, the independent authority on Non-Human Identity, IAM and Agentic AI security. nhimg.org