HTMLIframe Security (sandbox, loading)

Iframe Security

An <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
Adding 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

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

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

allow-scripts + allow-same-origin together can defeat the sandbox
Combining these two tokens lets the embedded document remove its own 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

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"
Setting 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

HTML
<iframe
  src="https://maps.example.com/embed?q=Berlin"
  title="Map of Berlin"
  loading="lazy"
  width="600"
  height="400"
></iframe>
Clickjacking
Clickjacking tricks a user into clicking something on your site while it's invisibly framed inside an attacker's page—for example, a transparent iframe of your "Delete account" button stacked under a fake "Claim your prize" button. Because the attack frames your page inside their page, no attribute on your own <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

Text
X-Frame-Options: DENY
Content-Security-Policy: frame-ancestors 'self'
Set both headers where it matters
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.
Sandboxing protects your page from the iframe; frame headers protect your page from being iframed
These are two different directions of trust. Use 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

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>
Notice what's deliberately absent: no 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

Default to the strictest sandbox that still works
Start with a bare 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.