An empty sandbox attribute blocks scripts, forms, popups, modals, downloads and top-level navigation, and gives the frame an opaque null origin. Each token, such as allow-scripts or allow-popups, re-enables one capability. Never combine allow-scripts and allow-same-origin on same-origin content: the frame can remove its own sandbox.
What does the iframe sandbox attribute do?
sandbox works as deny-by-default. Add the attribute with no value and the framed document loses almost every active capability. Add tokens to give specific ones back.
<iframe src="https://widgets.example.net/embed" sandbox title="Widget"></iframe>
With an empty sandbox, the framed page:
- can't run JavaScript: no inline or external scripts, event handlers or
javascript:URLs; - can't submit forms;
- can't open popups or new windows, including
target="_blank"links; - is given an opaque origin, so it can't use its own cookies,
localStorageor other storage, and every request it makes is cross-origin; - can't navigate the top-level page;
- can't show
alert(),confirm(),prompt()orprint()dialogs; - can't start downloads, lock the pointer or screen orientation, or use the Presentation API;
- can't request storage access or set
document.domain.
HTML and CSS still render, images load, and links that navigate the frame itself still work. The restrictions also apply to any iframes nested inside the sandboxed one.
Editing the sandbox attribute of an iframe that has already loaded doesn't change the current document. The new flags take effect the next time the frame navigates, so reset src after changing tokens with JavaScript.
Iframe sandbox tokens: full reference
Tokens are space-separated and case-insensitive. Browsers ignore tokens they don't recognise, so a newer token is harmless in an older browser; it just won't grant anything there.
| Token | What it re-enables | When to use it |
|---|---|---|
allow-scripts | JavaScript execution (but not popups) | Almost any interactive widget or app |
allow-same-origin | The document keeps its real origin instead of an opaque one, so cookies, storage and same-origin requests work | Trusted cross-origin content that needs its own login or storage |
allow-forms | Form submission | Contact forms, search boxes, checkout and login forms |
allow-popups | window.open(), target="_blank" and similar; popups inherit the sandbox | Widgets with external links or share buttons |
allow-popups-to-escape-sandbox | Popups open without inheriting the sandbox | When the opened page must work normally, such as a vendor site or bank verification window |
allow-modals | alert(), confirm(), prompt(), print() | Legacy apps that rely on native dialogs, or a print button |
allow-downloads | File downloads started from the frame | Report exports, invoice PDFs, file managers |
allow-top-navigation | Navigating the top-level page at any time | Rarely; prefer the user-activation version |
allow-top-navigation-by-user-activation | Navigating the top-level page, but only after a user gesture in the frame | "Open full app" buttons, redirect-based login or checkout after a click |
allow-top-navigation-to-custom-protocols | Handing off to non-HTTP schemes such as mailto:, tel: or an app's custom URL scheme, without other top navigation | "Open in app" and email links inside an embed |
allow-storage-access-by-user-activation | Calling document.requestStorageAccess() after a user gesture | Embeds that reuse a first-party login via the Storage Access API |
allow-pointer-lock | Pointer Lock API | Games, 3D viewers |
allow-orientation-lock | Locking screen orientation | Mobile games and video players |
allow-presentation | Presentation API (casting to a second screen) | Slide or media players that support casting |
Some of the newer tokens, such as allow-downloads, allow-storage-access-by-user-activation and allow-top-navigation-to-custom-protocols, arrived in browsers at different times. Test in the browsers you support.
Why allow-scripts plus allow-same-origin can break the sandbox
This pair is the most common sandbox mistake. When the framed content is same-origin with the parent, a script inside it has full access to the parent's DOM, including the <iframe> element that holds it. It can simply remove the sandbox:
<!-- Parent page on https://example.com -->
<iframe src="/uploads/user-page.html" sandbox="allow-scripts allow-same-origin"></iframe>
// Script inside /uploads/user-page.html
const me = window.frameElement; // accessible because we are same-origin
me.removeAttribute('sandbox');
me.src = me.src; // reload with no sandbox at all
// Or skip that and act on the parent directly
parent.document.body.innerHTML = '<h1>Hijacked</h1>';
The same applies to srcdoc iframes, which inherit the parent's origin. Chrome logs a console warning when it sees both tokens together for this reason.
For cross-origin content the pair is common and reasonable. A frame from widgets.example.net can't reach a parent on example.com either way, and allow-same-origin just lets it keep its own cookies and storage. The sandbox then limits popups, navigation, forms and dialogs, which is still useful.
If the content is untrusted, it gets allow-scripts or allow-same-origin, never both. If you need both for your own content, serve it from a separate origin, such as a dedicated user-content domain.
What an opaque origin breaks: cookies, storage and CORS
Without allow-same-origin, the document gets a unique opaque origin that serializes as null. That's the core of the sandbox's isolation, and it has side effects that often look like bugs:
- Cookies: reading or writing
document.cookiethrows aSecurityError. A login that works unsandboxed disappears. - Storage: accessing
localStorageorsessionStoragethrows aSecurityError, and IndexedDB is unavailable. Libraries that touch storage on startup can crash the whole script. - fetch and XHR: every request, even to the frame's own server, is cross-origin and carries
Origin: null. It needs CORS headers, and cookies aren't sent by default. - postMessage: messages from the frame arrive with
event.origin === "null".
// Inside a frame sandboxed without allow-same-origin
try {
localStorage.setItem('theme', 'dark');
} catch (err) {
console.log(err.name); // "SecurityError"
}
const res = await fetch('https://api.example.net/data');
// Request header: Origin: null
It makes the request work, but every sandboxed frame and data: URL on the web also sends Origin: null, so you've effectively allowed everyone. Authenticate with a token instead, or use allow-same-origin for trusted cross-origin content.
For messaging, the parent can't check event.origin against an allowlist when it's "null". Identify the frame with event.source === iframe.contentWindow, and since you can't target an opaque origin, only send it data that is safe to expose. The postMessage guide shows the full validation pattern, and why iframe cookies stop working covers the separate third-party cookie rules that apply even without a sandbox.
Popups and top navigation in a sandboxed iframe
allow-popups and allow-popups-to-escape-sandbox
Without allow-popups, window.open() returns null and target="_blank" links do nothing, which users read as a broken button. Adding allow-popups fixes that, but the new window inherits the sandbox. If your iframe lacks allow-scripts, the page it opens can't run scripts either, so a vendor's site or a payment page opens half broken.
allow-popups-to-escape-sandbox lets the popup load as a normal, unsandboxed page. That's usually what you want for outbound links, since the new tab has its own origin and can't touch your page.
<iframe src="https://widgets.example.net/embed" title="Widget"
sandbox="allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox"></iframe>
Top navigation variants
| Tokens | Can the frame change the parent page's URL? |
|---|---|
| None | No. Setting top.location is blocked and an error is logged. |
allow-top-navigation-by-user-activation | Only in response to a user gesture in the frame, such as a click |
allow-top-navigation | Yes, at any time, including on load |
allow-top-navigation-to-custom-protocols | Only to hand off to external protocol handlers like mailto: |
allow-top-navigation lets the frame silently send your visitors anywhere, including a look-alike phishing page. Use the user-activation version, and don't combine the two: the unrestricted token makes the other pointless.
Sandbox and frame-busting
Frame-busting is old JavaScript that tries to escape an iframe:
if (window.top !== window.self) {
window.top.location = window.self.location;
}
In a sandboxed iframe without a top navigation token, that assignment is blocked, so the page stays framed. Leave out allow-scripts and the script never runs at all, though the page probably won't work either. This is exactly why JavaScript frame-busting is weak protection. Sites that don't want to be framed should send CSP frame-ancestors or X-Frame-Options, which no sandbox can override; see clickjacking protection and X-Frame-Options vs frame-ancestors. If a page won't load in your iframe at all, the cause is usually those headers, covered in iframe "refused to connect".
How sandbox compares to CSP sandbox and the allow attribute
The CSP sandbox directive (header form)
A server can sandbox its own response with a Content-Security-Policy header. It takes the same tokens:
Content-Security-Policy: sandbox allow-scripts allow-forms
The difference is who sets it and where it applies. The sandbox attribute is chosen by the embedding page and only affects that one iframe. The CSP sandbox directive is chosen by the page's own server and applies wherever the document loads, including when someone opens it directly in a tab. That makes it the right tool for serving user uploads, such as HTML or SVG files, from your own domain. It's ignored in a <meta> tag and isn't enforced from Content-Security-Policy-Report-Only, so send it as a real header.
# Nginx: sandbox everything under /uploads/
location /uploads/ {
add_header Content-Security-Policy "sandbox" always;
}
If both apply, the restrictions add up. A capability has to be allowed by the attribute and by the header.
sandbox vs the allow attribute
The two attributes sit side by side and control different things. sandbox removes core document capabilities. allow is Permissions Policy: it delegates powerful features that cross-origin iframes don't get unless you list them.
sandbox | allow | |
|---|---|---|
| Model | Everything off, tokens opt back in | Features listed are delegated to the frame |
| Controls | Scripts, forms, popups, origin, navigation, dialogs, downloads | fullscreen, autoplay, camera, microphone, geolocation, clipboard-write, encrypted-media, picture-in-picture, web-share, payment, storage-access and more |
| Without the attribute | No restrictions | Default policy; many features blocked for cross-origin frames |
Neither can do the other's job: no sandbox token grants camera access, and no allow feature turns scripts back on. A video call embed might need both:
<iframe src="https://meet.example.net/room/123" title="Video call"
sandbox="allow-scripts allow-same-origin allow-forms allow-popups"
allow="camera; microphone; fullscreen; clipboard-write"></iframe>
allowfullscreen is the legacy attribute for fullscreen; allow="fullscreen" is the current form.
Iframe sandbox recipes you can copy
Untrusted user HTML (srcdoc + sandbox)
For previews of user-written HTML, email bodies or code playgrounds, render into a srcdoc iframe with allow-scripts at most, and never allow-same-origin. A srcdoc document inherits the parent's origin, so adding that token would give user code full access to your page.
<iframe id="preview" title="Preview" sandbox="allow-scripts"
style="width:100%;height:400px;border:0"></iframe>
<script>
const csp = '<meta http-equiv="Content-Security-Policy" ' +
'content="default-src \'none\'; script-src \'unsafe-inline\'; style-src \'unsafe-inline\'; img-src data:">';
// Setting the property avoids HTML-escaping the markup into an attribute
document.getElementById('preview').srcdoc = csp + userHtml;
</script>
The prepended CSP stops the preview from loading remote scripts, styles or images and from making fetch requests. It doesn't stop the frame navigating itself, so treat it as defence in depth. If the content doesn't need scripts at all, use an empty sandbox. For heavier cases, host user content on a separate domain and add the CSP sandbox header too.
Third-party widgets
Chat, forms, maps and social embeds usually need scripts, their own cookies, forms and outbound links. Because they're cross-origin, allow-scripts allow-same-origin is acceptable here:
<iframe src="https://widgets.example.net/chat" title="Support chat" loading="lazy"
sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox"></iframe>
This still blocks top navigation, dialogs and downloads. If something breaks, add one token at a time rather than removing the sandbox.
Payment and auth flows
Checkout and sign-in embeds are the most fragile. They often open bank verification or OAuth windows, submit forms, rely on session cookies, and sometimes redirect the whole page after the user confirms. Check the provider's documentation first: many list the exact tokens they need or ask you not to sandbox their frame. A starting point:
<iframe src="https://pay.example.net/checkout" title="Checkout"
sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation allow-storage-access-by-user-activation"
allow="payment"></iframe>
Drop allow-storage-access-by-user-activation if the flow doesn't use the Storage Access API. For the basics of a clean embed, see how to embed a website in HTML.
How to test sandbox tokens live
Reading the spec only gets you so far; the quickest way to learn which tokens a page needs is to watch it fail.
- Open the testiframe.com iframe tester and load the page you want to embed.
- In the Frame settings tab, tick Enable sandbox. It starts with a common set ticked (
allow-scripts,allow-same-origin,allow-forms,allow-popups). Untick everything to see the empty sandbox. - Add tokens one at a time. The preview reloads after each change, so you see immediately what each token restores.
- Open your browser's DevTools console alongside. Blocked actions log errors that usually name the missing capability.
- Copy the final
<iframe>from the Embed code tab. It includes your sandbox,allowand referrer settings.
The same tab lets you toggle allow features like camera, fullscreen and storage-access, so you can test both attributes together.
FAQ
What does an empty sandbox attribute do?
An empty sandbox attribute applies every restriction: no scripts, no form submission, no popups, no modal dialogs, no downloads, no top-level navigation, and the page gets an opaque origin, so it can't use its own cookies or storage. HTML and CSS still render. Each token you add re-enables one capability.
Is sandbox allow-scripts allow-same-origin safe?
Not for content from your own origin, including srcdoc iframes. With both tokens, a same-origin frame can reach its own iframe element, remove the sandbox attribute and reload itself, or act on the parent page directly. For cross-origin content the pair is common, because the frame still can't touch the parent, but the sandbox then only limits things like popups, navigation and forms.
Why can't my sandboxed iframe use cookies or localStorage?
Without allow-same-origin the frame gets an opaque origin, serialized as null. Accessing document.cookie or localStorage throws a SecurityError, and fetch requests are treated as cross-origin with Origin: null. Add allow-same-origin if the content is cross-origin and trusted enough, or pass data in with postMessage instead.
How do I let links in a sandboxed iframe open in a new tab?
Add allow-popups so target="_blank" links and window.open() work. Popups inherit the sandbox by default, so add allow-popups-to-escape-sandbox as well if the new tab must behave like a normal page, for example a vendor site or a payment page that needs scripts.
Can a sandboxed iframe redirect the parent page?
Only if you allow it. Without a top navigation token, attempts to change top.location are blocked. allow-top-navigation-by-user-activation permits it only after a click inside the frame, and allow-top-navigation permits it at any time. Prefer the user-activation version.
Can sandbox bypass X-Frame-Options or frame-ancestors?
No. The sandbox attribute only adds restrictions to the framed page. If the page sends X-Frame-Options or a CSP frame-ancestors directive that excludes your site, the browser refuses to render it no matter which sandbox tokens you set. Sandbox can stop JavaScript frame-busting, but not header-based protection.