Iframe Security
<iframe> can load content from any origin, including ones you don't control. Left unrestricted, that embedded document can run scripts, open popups, submit forms, and even trick users into clicking something they didn't mean to. HTML gives you several attributes—plus a couple of server-side headers—to lock this down.The sandbox Attribute
sandbox to an iframe applies a strict set of restrictions to the embedded document: no scripts, no forms, no popups, treated as coming from a unique opaque origin, and more. You then selectively re-enable only what you need by listing tokens.sandbox-empty.html
<!-- Maximally restricted: no scripts, no forms, no same-origin access --> <iframe src="https://untrusted.example.com/widget" sandbox title="Untrusted widget"></iframe>
sandbox-tokens.html
<!-- Allow scripts and same-origin access, but nothing else --> <iframe src="https://widgets.example.com/chart" sandbox="allow-scripts allow-same-origin" title="Chart widget" ></iframe>
Token | Grants |
|---|---|
allow-scripts | Permits JavaScript execution inside the frame |
allow-same-origin | Lets the frame be treated as its own origin (cookies, storage) |
allow-forms | Permits form submission |
allow-popups | Permits window.open() and similar popups |
allow-top-navigation | Permits the frame to navigate the top-level page |
allow-modals | Permits alert(), confirm(), and other modal dialogs |
sandbox restriction via script, effectively unsandboxing itself. Only combine them for content you trust.The allow Attribute (Permissions Policy)
allow grants (or explicitly withholds) access to browser features such as the camera, microphone, geolocation, or fullscreen API, scoped to just that iframe.allow-attribute.html
<iframe src="https://meet.example.com/room/123" title="Video call" allow="camera; microphone; fullscreen" sandbox="allow-scripts allow-same-origin allow-forms" ></iframe>
loading="lazy"
loading="lazy" defers fetching an iframe's content until it's about to enter the viewport, which reduces initial page weight and speeds up load for pages with many embeds (maps, video players, ads).lazy-iframe.html
<iframe src="https://maps.example.com/embed?q=Berlin" title="Map of Berlin" loading="lazy" width="600" height="400" ></iframe>
Clickjacking
<iframe> tags can stop it—the protection has to come from the page being framed.Server-Side Mitigation
Two HTTP response headers tell browsers whether your pages are allowed to be framed by others at all.
Header | Effect |
|---|---|
X-Frame-Options | DENY blocks all framing; SAMEORIGIN allows framing only by your own origin (legacy but widely supported) |
Content-Security-Policy: frame-ancestors | Modern replacement—e.g. frame-ancestors 'self' lets only your own origin frame the page; a list of origins can be whitelisted |
response-headers.http
X-Frame-Options: DENY Content-Security-Policy: frame-ancestors 'self'
frame-ancestors is the modern standard and takes precedence when both are present, but sending X-Frame-Options too keeps very old browsers protected. Apply this to any page with sensitive actions—account settings, payments, admin panels.sandbox and allow when you embed someone else's content. Use X-Frame-Options/frame-ancestors to control whether others can embed your pages.A Worked Example: Embedding a Third-Party Widget
Putting the pieces together, a realistic embed of an untrusted third-party widget locks down scripting, forms, and popups to only what's actually required, grants no extra browser permissions, and defers loading until it's near the viewport.
worked-example.html
<iframe src="https://widgets.example.com/comments?post=42" title="Comments widget" sandbox="allow-scripts allow-forms allow-popups" allow="clipboard-write" loading="lazy" width="100%" height="500" ></iframe>
allow-same-origin (this widget doesn't need to read cookies or storage on its own origin from within the sandbox), no camera/microphone in allow, and no allow-top-navigation (it can't redirect the whole page).Quick Reference
Goal | Mechanism |
|---|---|
Restrict what an embedded document can do | sandbox attribute with only the required tokens |
Grant specific browser features to an iframe | allow attribute (camera, microphone, fullscreen, etc.) |
Defer loading off-screen iframes | loading="lazy" |
Prevent your own pages from being framed elsewhere | X-Frame-Options / Content-Security-Policy: frame-ancestors response headers |
sandbox attribute (no tokens at all) and add tokens one at a time only when something breaks that you've confirmed the widget actually needs. This keeps the blast radius of any bug in the embedded content as small as possible.sandbox restricts what an embedded document can do; start from no tokens and add only what is required.
allow-scripts plus allow-same-origin together can let embedded content escape its sandbox—avoid combining them for untrusted content.
allow grants specific browser permissions (camera, microphone, fullscreen) scoped to the iframe.
loading="lazy" defers loading off-screen iframes for performance.
Clickjacking is mitigated on the server with X-Frame-Options and/or CSP frame-ancestors, not with iframe attributes.