What an Embedded Third-Party Script Actually Gets
SnapFrom Team · · 4 min read
Two lines of HTML, both described as “embedding a third party”:
<script src="https://widget.example/w.js"></script>
<iframe src="https://widget.example/w.html"></iframe>
They read as variations on the same idea. In terms of what the other party ends up holding, they are close to opposites, and the difference is not a matter of degree.
The policy that everyone half-remembers
The same-origin policy “is a critical security mechanism that restricts how a document or script loaded by one origin can interact with a resource from another origin”[1]. That sentence is where most people’s understanding stops, and it leaves an impression that cross-origin things are held apart by default.
The next thing MDN says is the part that matters: “cross-origin embedding is typically allowed”[1], and JavaScript loaded through a src attribute is on the list of what is allowed[1].
The policy governs what one origin can read from another. It does not sandbox code you have chosen to load. Once the browser fetches that file, it is running as part of your document, not as a visitor to it.
The one asymmetry MDN does record for cross-origin scripts is small and points the other way: “error details for syntax errors are only available for same-origin scripts”[1]. That is the extent of the separation. You get less debugging information about the third party’s code, and it gets everything.
The iframe is the actual boundary
An iframe is where the policy does the thing people imagine it doing everywhere. Anything embedded by an iframe is subject to cross-origin restrictions[1], and for the APIs that let two documents reach each other - iframe.contentWindow, window.parent, window.open, window.opener - MDN is explicit that “when two documents do not have the same origin, these references provide very limited access” to Window and Location[1].
Different origin, different document, no reach into your DOM. That is a real boundary, and it is why a payment field in an iframe and a payment field rendered by an embedded script are not comparable designs.
The sandbox attribute tightens it further. Its value “can either be empty to apply all restrictions, or space-separated tokens to lift particular restrictions”[3], which is a nice inversion: the secure position is the one where you write the least.
There is one combination MDN singles out. When the embedded document has the same origin as the embedding page, “it is strongly discouraged to use both allow-scripts and allow-same-origin, as that lets the embedded document remove the sandbox attribute - making it no more secure than not using the sandbox attribute at all”[3]. A sandbox that the guest can take off is decoration.
What the script tag is really a decision about
Because a third-party script runs as part of your page, the risk is not what that company intends. It is who controls the file.
MDN states it directly: loading a script this way “comes with a risk, in that if an attacker gains control of the third-party host, then they can inject arbitrary malicious content into its files (or replace the files completely)”[2]. The name for that is a supply chain attack[2].
Note what is being trusted. Not the vendor’s good faith. The vendor’s build pipeline, their hosting, their CDN account, their npm credentials, and every future version of a file that will be swapped under the same URL without your involvement.
The one thing that pins it down
Subresource Integrity exists for exactly this. It “is a defense against attacks such as this, by ensuring that the files your web application fetches have exactly the contents that you expect them to have”, and it “works by allowing you to provide a cryptographic hash that a fetched resource must match”[2].
The enforcement is unambiguous. Before executing the script the browser compares it to the expected hashes, and if it does not match, “it will refuse to load the resource, and return a network error”[2].
That is a strong guarantee with a specific shape: it pins the bytes, not the behaviour. A script that was always malicious will hash correctly and run. What SRI removes is the silent swap, which is the part you cannot otherwise observe.
What this costs you
A script tag is an authorisation, not an inclusion. The same-origin policy will not stand between your page and code you asked for[1].
An iframe costs you integration and buys you a boundary. It cannot restyle itself to match your page or read a value out of your form, and that inability is the entire product.
SRI is cheap and narrow. One attribute, and the pinning is enforced by refusing to load[2]. It cannot be applied to a script that legitimately changes on every request, which is precisely how most analytics and tag-manager snippets are distributed.
The question worth asking before adding either line is not “do I trust this company”. It is “what does this specific mechanism let them reach, and what happens if someone else gets their publishing credentials”.
Sources
- Same-origin policy
- MDN Web Docs, Mozilla, accessed
Supports: The same-origin policy 'is a critical security mechanism that restricts how a document or script loaded by one origin can interact with a resource from another origin', but 'cross-origin embedding is typically allowed', with JavaScript loaded via a script src listed as an example of permitted cross-origin embedding, where 'error details for syntax errors are only available for same-origin scripts'. Anything embedded by an iframe is subject to cross-origin restrictions, and for APIs such as iframe.contentWindow, window.parent, window.open and window.opener, 'when two documents do not have the same origin, these references provide very limited access' to Window and Location objects.
- Subresource Integrity
- MDN Web Docs, Mozilla, accessed
Supports: Loading a script from a third party 'comes with a risk, in that if an attacker gains control of the third-party host, then they can inject arbitrary malicious content into its files (or replace the files completely)', which MDN names a supply chain attack. Subresource Integrity 'is a defense against attacks such as this, by ensuring that the files your web application fetches have exactly the contents that you expect them to have', and 'works by allowing you to provide a cryptographic hash that a fetched resource must match'; before executing the script the browser compares it to the expected hashes, and if it does not match 'it will refuse to load the resource, and return a network error'.
- The iframe element
- MDN Web Docs, Mozilla, accessed
Supports: The sandbox attribute's value 'can either be empty to apply all restrictions, or space-separated tokens to lift particular restrictions'. MDN warns that when the embedded document has the same origin as the embedding page, 'it is strongly discouraged to use both allow-scripts and allow-same-origin, as that lets the embedded document remove the sandbox attribute - making it no more secure than not using the sandbox attribute at all'.