Put the vendor script in a frame on a separate domain, let the two sides talk only through postMessage, and check the sender's origin on every message you receive. Accept a small, fixed set of message types and ignore anything else. The frame isolates code, and the checks protect the channel.
The pattern
The OWASP Third Party JavaScript Management Cheat Sheet describes putting vendor JavaScript into an iframe from a different domain, such as a static data host. The frame works as a jail: the vendor script has no direct access to the host page's DOM and cookies. The main page and the frame communicate through the postMessage mechanism.
The cheat sheet's example also shows the key defensive line. The listener in the frame receives messages and returns immediately if the event origin does not match the expected host. Only then does it build the DOM or initialize other data the third-party code needs.
It also notes that iframes can be secured further with the sandbox attribute, and that for high risk applications Content Security Policy should be used in addition.
What can go wrong
| Mistake | Result |
|---|---|
| No origin check on incoming messages | Any page can send a message your code trusts |
| Sending to every origin | Data goes to whoever loaded the page |
| Passing whole objects from the page | The widget sees more data than it needs |
| Acting on any message type | An attacker can trigger unintended actions |
| Allowing scripts and same origin together on your own origin | The sandbox can be removed |
MDN's iframe documentation states the last point directly: when an embedded document has the same origin as the embedding page, using both allow-scripts and allow-same-origin is strongly discouraged, since the document can remove the sandbox attribute.
Design the message contract
- List the messages. For a review widget that might be: ready, resize, and product-id from the page.
- Define fields. Fixed names and simple values. No raw HTML, no functions.
- Check origin on receive. Compare the origin to an exact expected value, not a pattern.
- Name the target on send. When posting to the frame, give its exact origin instead of a wildcard.
- Validate content. Treat message data as untrusted input.
- Ignore the rest. Unknown types do nothing.
Test the boundary
Write a small test page on a different origin that sends messages to your widget host and to your main page. Confirm they are ignored. Repeat after any change to the widget or message list.
When this is worth the effort
An isolated frame costs development time and can limit what a widget can do. It is most valuable for tags that must run on sensitive pages and cannot be replaced, such as a vendor script with obfuscated code. For simple, low-risk embeds, a sandbox attribute plus a policy may be enough. See our guide on which iframe sandbox tokens a store widget needs.
Try it yourself
Click through it at your own pace, or open it full screen. Look at how a finding links to the exact line, which is how you would track down a missing origin check.
Where Safeguard fits
Safeguard's SBOM and dependency scanning covers packages used by your widget host code, and its policies can enforce rules on them. The message contract itself is design work for your team. A free scan is at gold.safeguard.sh/scan.
Quick check
Is an origin check enough on its own?
No. Also limit the message types, validate the contents and keep the frame sandboxed.
Can the widget read our cookies from inside the frame?
Not through the page's DOM when the frame is on a separate origin, per OWASP. Do not pass cookie values in messages.
Sources
Last reviewed: 19 October 2026.