Safeguard
Application Security•

ASVS 5.0 V3.5.5: Validate Origin and Shape of postMessage Data

ASVS V3.5.5 (Level 2) says drop postMessage events from untrusted origins or with invalid syntax. Safe listener and sender code, tests, and the mistakes behind real bugs.

Safeguard Security Team
4 min read

ASVS 5.0 requirement V3.5.5 says messages received through the postMessage interface must be discarded if the origin of the message is not trusted, or if the message syntax is invalid. It is a Level 2 requirement.

That covers two checks in every message listener: compare event.origin to an exact trusted value, then validate the data before using it.

Why it matters

window.postMessage lets pages in different origins talk, for example a parent page and an embedded widget or a sign in popup. A listener without checks accepts messages from any window that has a reference to yours, and any site can open or embed your page and send it data. See CWE-346 for origin validation errors.

What happens next depends on your handler. If it writes event.data.html into innerHTML, you have DOM XSS. If it reads event.data.redirect and sets location, you have an open redirect. If it treats a message as "user is authenticated" or "payment complete", an attacker can fake the state.

How to implement it

A defensive listener checks origin first, then shape, then acts:

const TRUSTED = "https://widget.example.com";

window.addEventListener("message", (event) => {
  if (event.origin !== TRUSTED) return;              // exact match, never indexOf or endsWith

  const msg = event.data;
  if (typeof msg !== "object" || msg === null) return;
  if (msg.type !== "resize" || !Number.isInteger(msg.height)) return;
  if (msg.height < 0 || msg.height > 5000) return;

  document.getElementById("frame").style.height = msg.height + "px";
});

Notes on that code:

  • Compare with !== against a full origin string. Substring checks accept https://widget.example.com.evil.net.
  • Validate type, fields and ranges, and ignore anything unexpected. Do not pass data to innerHTML, eval or location.
  • Use textContent if you must display a string.

When sending, always name the target origin and never use the wildcard:

iframe.contentWindow.postMessage({ type: "init", userId: 42 }, "https://widget.example.com");

A "*" target means any origin that now occupies that window receives the data.

If a message carries something sensitive, also check event.source === iframe.contentWindow so you only accept messages from the frame you created.

Click through it at your own pace, or open it full screen.

How to test it

Static review first: search the codebase for addEventListener("message" and onmessage, and confirm each handler begins with an origin check. A code scan finds listeners lacking one; the demo above shows how Safeguard's static analysis surfaces them.

Then test dynamically. Host a page on a different origin that opens or frames yours and sends messages from the console:

const w = window.open("https://app.example.com/");
setTimeout(() => {
  w.postMessage({ type: "resize", height: 99999 }, "*");
  w.postMessage({ type: "resize", height: "<img src=x onerror=alert(1)>" }, "*");
  w.postMessage("not an object", "*");
}, 3000);

None of these may change the page, throw an uncaught error, or run script. Repeat with malformed JSON strings if your handler parses text.

Common mistakes

No origin check at all. The most common issue.

Loose matching. indexOf, endsWith, regexes without anchors.

Trusting data after the origin check. A trusted origin can still be compromised or buggy, so validate shape anyway.

Wildcard target in postMessage. Always specify the exact origin.

Checking event.origin after using the data. Order matters.

Quick check

Is checking event.source enough?

No. It tells you which window sent the message, not which origin that window currently has. Check origin too.

What if the sender is another page of mine?

Still check the origin. Your own pages can contain XSS, and the check keeps one compromised page from commanding another.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.