How Do You Stop a Third Party Embed Doing Whatever It Wants?
How do you stop a third party embed from doing whatever it wants?
With two mechanisms that most marketing sites never turn on. The sandbox attribute on an iframe removes capabilities from the embedded document, and the Permissions-Policy response header controls which browser features any part of your page, including embeds, is allowed to reach at all.
By default, an iframe you drop onto a page can run scripts, submit forms, open popups, navigate your top level page, and ask the user for their camera. You almost certainly did not intend to grant all of that to a booking widget.
This is not exotic security work. It is two attributes and one header, and on most B2B sites it takes an hour. We do it on every build, and the reason is simple: you control which third parties you embed, but you do not control what they ship next month.
What is the default, and why is it so permissive?
The default is full capability, because iframes predate the modern threat model by about two decades. An embedded document without a sandbox attribute behaves like an ordinary page that happens to be inside yours, with its own scripts, its own storage, and the ability to navigate the browser.
That was fine when an iframe usually held your own content. It is not fine when it holds a calendar widget, a chat tool, a video player and a review carousel, each maintained by a company whose roadmap you do not read.
The performance side of this is well covered, and we wrote about it in what third party scripts really cost. This piece is the capability side, which gets far less attention.
What does the sandbox attribute actually do?
An empty sandbox attribute applies all restrictions. You then add space separated tokens to lift the specific ones the embed genuinely needs. That inversion is the important part: you start from nothing and grant, rather than starting from everything and blocking.
The common tokens are allow-scripts, which permits script execution, allow-forms, which permits form submission, allow-popups, which permits window.open and links targeting a new window, and allow-modals, which permits alert, confirm, print and prompt.
There are more specialised ones. allow-downloads permits downloads from a link with the download attribute. allow-top-navigation permits the embed to navigate your top level page, and allow-top-navigation-by-user-activation permits it only after a user gesture, which is almost always the one you want if you need it at all.
What is the one combination to be careful about?
allow-scripts together with allow-same-origin, when the embedded document has the same origin as your page. MDN's warning is direct: that combination lets the embedded document remove the sandbox attribute, making it no more secure than not using sandbox at all.
Read that twice, because it is the trap. A developer debugging a broken embed adds tokens one at a time until it works, lands on both of these, and ships something that looks locked down in the markup and is not.
For genuinely third party origins the combination is less dangerous, since same-origin refers to the embed's own origin rather than yours. It is still worth knowing why the rule exists rather than copying a token list from an old answer.
What does Permissions-Policy add on top?
It controls browser features rather than document capabilities. The header provides a mechanism to allow and deny the use of browser features in a document or within any iframe elements in that document, which means it reaches things sandbox does not: camera, microphone, geolocation, payment, and dozens more.
MDN lists over 50 directives. The ones a marketing site should care about are camera, microphone, geolocation, payment, display-capture for screen capture, and usb, serial and hid for hardware access. There is no reason for a corporate website to leave any of those open.
The newer directives are a good argument for setting this now rather than later. The list has grown to include things like storage-access, idle-detection and a set of on device model directives such as language-model, translator and summarizer. Features keep arriving, and a default deny policy covers the ones you have not heard of yet.
How is the header written?
Each directive takes an allowlist in parentheses. An empty allowlist, written as a pair of parentheses with nothing inside, disables the feature in all browsing contexts. The keyword self allows it in the current document and same origin iframes. A quoted origin allows a named third party. An asterisk allows everything, which is the value you should almost never write.
So a sensible starting line looks like camera=(), microphone=(), geolocation=(), payment=(), display-capture=(). Directives are separated by commas, and you can also send several separate Permissions-Policy headers rather than one long one.
When you do need to grant something, grant it narrowly. Writing geolocation=(self "https://maps.example.com") allows your own page and one named origin, and nothing else.
How do the header and the iframe allow attribute work together?
Both must agree. For an iframe to have a feature enabled, its origin has to be permitted by the parent page's Permissions-Policy header and by that iframe's allow attribute. The header sets the outer limit and the attribute grants within it.
This catches people out in both directions. A permissive header does nothing if the iframe has no allow attribute. A generous allow attribute does nothing if the header disabled the feature site wide. Debug both before assuming the feature is broken.
Inside the allow attribute, the default allowlist is src, meaning the origin in the iframe's own src. So allow="geolocation" and allow="geolocation 'src'" mean the same thing, which is a small detail worth knowing when reading someone else's markup.
How do you roll this out without breaking a live site?
Use the report only mode first. MDN documents a Permissions-Policy-Report-Only header for exactly this, letting you test a policy without enforcing it. Pair it with Reporting-Endpoints and you get violation reports for the features your site actually uses before anything breaks.
The reports tell you the truth that an audit will not. Marketing tools change behaviour between versions, and a widget that never asked for a microphone last quarter may ask this quarter.
This is the same staged approach that works for other security headers, and the pattern is identical to the one in rolling out a content security policy. Report, read, then enforce.
What else should be on every third party iframe?
Three attributes beyond sandbox. Set loading to lazy so the embed does not load until it approaches the viewport, which is free performance on anything below the fold. Set an explicit referrerpolicy, because the default sends your full URL to same origin and the origin to cross origin destinations, and you may not want either.
For referrerpolicy, strict-origin is a good default on third party embeds: it sends only the origin, and only when the security level is unchanged. Avoid unsafe-url entirely, since it sends the full URL including the path.
The third is credentialless, which loads the frame in an ephemeral context with no access to the embedding site's network, cookies or storage data. It is useful when you want an embed visible but entirely cut off from anything stateful.
Which embeds should you simply not use?
Any that break when sandboxed with a reasonable token set, and cannot tell you why. That is a useful filter. A well built widget documents what it needs. A widget that requires allow-same-origin, allow-scripts, allow-top-navigation and a wildcard permissions policy is telling you something about how it was built.
The second category is anything that only works when loaded as a script in your page rather than in an iframe. A script has your full page context by definition, and no sandbox applies. Prefer the iframe version when a vendor offers both, even if it is slightly uglier.
That trade off, a little visual compromise for a hard security boundary, is one we take nearly every time, and it sits alongside the broader checklist in our guide to website security basics.
What should you check on your own site today?
Open your response headers and look for Permissions-Policy. On most marketing sites it is absent. Then view source and count your iframes, and check how many have a sandbox attribute. On most sites the answer is none.
Fix it in that order. Add a deny by default Permissions-Policy in report only mode, read the reports for a week, then enforce. Separately, sandbox each iframe starting from empty and add back only what actually breaks.
One caveat worth stating plainly: MDN notes that Permissions-Policy is not Baseline and does not work in all major browsers, so treat it as defence in depth rather than a guarantee. Sandbox is the older and more widely supported of the two, which is why we start there.
We set both up as part of every build, because retrofitting them across a site full of embedded tools is much less pleasant than doing it once at the start. If you want someone to look at what your site currently grants, we are happy to check. You can find us at phoenix.studio.
Want a site that performs like this?
Tell us about your project. We will come back with a clear next step, no pressure.
This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.
Have a project like this?
Tell us where you want to go. We'll tell you how we'd get you there.