← Back to home
Reference · Sandbox

The iframe sandbox
attribute, explained.

The sandbox attribute is a deny-by-default capability switch on <iframe>. Writing sandbox with no value strips everything — scripts, forms, popups, storage, same-origin. You then add specific allow-* tokens to re-enable only what the embed actually needs.

Updated:11 May 2026Read:7 minLevel:Intermediate

What sandbox actually does

Without sandbox, an iframe runs with the same privileges as a normal top-level browsing context: it can run scripts, set cookies in its own origin, navigate the parent, open popups, submit forms, request fullscreen, and do anything else the browser allows.

With sandbox set (even to an empty string), the frame loses all of those privileges, plus its origin is replaced with a freshly-generated unique opaque value. Two iframes pointing at the same URL with empty sandboxes cannot see each other's storage. The frame cannot reach its own cookies even if the URL matches the parent.

Maximum restriction — useful for displaying untrusted user HTML.
<iframe sandbox srcdoc="<h1>Untrusted user content</h1>"></iframe>

Every sandbox flag

Add these as space-separated tokens to sandbox="…". Each re-enables one specific capability. Flags can be combined freely except for the dangerous pair flagged below.

FlagWhat it allowsNotes
(no value)Maximum restriction. Scripts disabled, forms disabled, plugins disabled, top-navigation disabled, popups disabled, downloads disabled. Origin is forced to a unique opaque value.
allow-scriptsRe-enable JavaScript inside the frame.Required for almost every realistic embed — but pairs dangerously with allow-same-origin.
allow-same-originRe-enable the frame's normal origin (otherwise it is a unique opaque origin even if the URL matches the parent).Combined with allow-scripts, the frame can rewrite its own sandbox attribute on a parent reference and break out. Avoid the pairing.
allow-formsRe-enable HTML form submission.
allow-popupsRe-enable window.open() and target="_blank" links.
allow-popups-to-escape-sandboxAny popup the frame opens is created without inheriting the sandbox.Use only when you genuinely need the popup to behave like a normal browser tab.
allow-modalsRe-enable alert(), confirm(), prompt(), beforeunload, print dialogs.
allow-top-navigationRe-enable the frame writing to window.top.location.Required for most OAuth flows that redirect the parent on success.
allow-top-navigation-by-user-activationTop-navigation is allowed only in response to a user gesture (click, keypress).Safer middle ground when you need top-navigation but want to block silent redirects.
allow-downloadsRe-enable file downloads triggered from inside the frame.
allow-presentationRe-enable the Presentation API (second-screen video).
allow-pointer-lockRe-enable element.requestPointerLock() (game-style mouse capture).
allow-orientation-lockRe-enable screen.orientation.lock().
allow-storage-access-by-user-activationThe frame can call document.requestStorageAccess() to get same-site cookies despite cross-origin embedding.Pairs with the Storage Access API. Required for most third-party login flows on Safari/iOS.

The combination you should not write

sandbox=“allow-scripts allow-same-origin” gives the frame back its own origin and the ability to run scripts. A script inside the frame can then reach window.frameElement (because it now has same-origin access to its parent), call frameElement.removeAttribute('sandbox'), force a reload, and come back fully unsandboxed.

The escape — runs inside the frame if both flags are present.
if (window.frameElement) {
  window.frameElement.removeAttribute('sandbox');
  location.reload();           // now reloaded without sandbox
}

The HTML spec explicitly warns about this. If you genuinely need both capabilities, host the frame on a different origin from the parent — then the frame cannot touch window.frameElement and the escape fails.

Recipes for real embeds

Untrusted user-generated HTML

Markdown previews, rich-text playgrounds, anything where you render content the user just typed. Strip everything, render via srcdoc so there is no separate URL to host:

<iframe sandbox srcdoc="${escapedUserHtml}"></iframe>

Third-party widget that needs to run scripts

A chat widget, a comments embed, a calculator. Allow scripts so it works, but keep it on its own origin so the dangerous escape fails:

<iframe
  src="https://widget.partner.example/chat"
  sandbox="allow-scripts allow-popups allow-forms"
  loading="lazy"
></iframe>

Payment iframe (Stripe Elements, similar)

Payment providers ship their own sandboxing in CSP, but layering yours costs nothing:

<iframe
  src="https://js.stripe.com/v3/elements-inner-payment-..."
  sandbox="allow-scripts allow-same-origin allow-forms allow-popups"
  allow="payment"
></iframe>

The allow-same-originhere is safe because the frame is on Stripe's origin, not yours — Stripe's code cannot reach your window.frameElement from a cross-origin context.

Embedded OAuth provider

SSO callbacks usually need to redirect the parent on success. Allow top-navigation only by user activation so a hostile redirect cannot fire silently:

<iframe
  src="https://login.provider.example/authorize?..."
  sandbox="allow-scripts allow-forms allow-top-navigation-by-user-activation"
></iframe>

Detecting sandboxed iframes on a page

For an audit, list every iframe with its sandbox flags:

Array.from(document.querySelectorAll('iframe')).map(f => ({
  src: f.src || '(srcdoc)',
  sandbox: f.getAttribute('sandbox'),
}));

The Iframe Detector Chrome extension shows the same information visually — including which frames are sandboxed and which are not — without typing.

Frequently asked questions

Together they re-grant the frame the two things that matter most: its real origin (so it can touch storage and same-site cookies) and the ability to run scripts. A script inside the frame can then reach into its own element via window.frameElement and remove the sandbox attribute, then trigger a reload — at which point the sandbox is gone. The HTML spec explicitly warns about this combination.

A unique, freshly-generated origin used only for that frame's lifetime. It cannot access cookies, localStorage, IndexedDB, or any other storage of the URL's real origin. Two iframes sandboxed without allow-same-origin even get different opaque origins from each other. That isolation is the whole point.

Yes. postMessage is the supported cross-origin communication channel and it works regardless of sandbox. Use it for any parent↔frame coordination. Just remember to validate the origin on the receiving side — the opaque origin shows up as 'null' in event.origin.

Yes — sandbox applies on top of the Same-Origin Policy. A cross-origin iframe is already isolated by SOP, but sandbox lets you additionally strip capabilities (forms, scripts, top-navigation, popups) from frames you control even when their origin is yours. Treat sandbox and SOP as orthogonal layers.

Run document.querySelectorAll('iframe[sandbox]') in DevTools — that returns every sandboxed iframe. For each one, check its sandbox attribute to see which flags are set. The Iframe Detector extension surfaces this in the popup list with a small badge so you can scan multiple frames at once.