Why do the <iframe> sandbox tokens allow-scripts and allow-same-origin, used together on content served from your own origin, amount to no sandbox at all?
answer
- origin separation is doing the work
- the frame can reach its own element
- attribute lives in the parent's markup
- flags are computed at navigation
- a path is not an origin
basics
~20 sTogether those tokens make the framed document same-origin with the embedder and let it run script, so its code can reach the parent document, delete the sandbox attribute from its own iframe element, and reload itself unsandboxed.
solid answer
~40 s`allow-same-origin` gives the framed document its real origin back instead of the sandbox's opaque one, and `allow-scripts` lets it execute code. If the framed content is served from the same origin as the embedding page, those two together mean the frame's script is same-origin with the parent: it can walk `window.parent.document`, find its own `<iframe>` element, call `removeAttribute('sandbox')`, and reload itself with every capability restored. It can also read the embedder's cookies and storage on the way. The sandbox is therefore only as strong as the origin separation behind it — the fix is to serve untrusted embedded content from a genuinely different origin, so that `allow-same-origin` restores an origin that has no power over yours. If you cannot do that, drop one of the two tokens.
go deeper
Recall that allow-same-origin gives the frame its real origin back and allow-scripts lets it run code, and that combining both on content from your own site is the documented unsafe pairing.
Explain the mechanism step by step: same-origin script reaches the parent document, finds its own frame element, removes the sandbox attribute, and reloads to get a document created without sandbox flags.
Show that the condition is shared origin, not the token pair, and that your fix is hosting untrusted embeds on a separate origin. Be ready to give the safe configuration for an untrusted HTML preview without hesitating.
Own the origin topology: decide that untrusted or user-authored content lives on a dedicated hostname so containment survives the tokens teams will inevitably need, and make that a platform default rather than a per-feature argument.
## The two tokens, separately A sandboxed `<iframe>` is placed in a **unique opaque origin** — an origin equal to nothing else — and has script execution disabled. Two tokens undo those specific restrictions: - `allow-same-origin` stops the origin substitution. The document keeps the origin it was actually served from. - `allow-scripts` re-enables script execution inside the frame. Each on its own is defensible. `sandbox="allow-scripts"` gives you a frame that can run code but is cross-origin to everything, has no cookies and no storage, and cannot touch the embedder's DOM. `sandbox="allow-same-origin"` gives you a frame with its real origin but no ability to run code. ## Why the combination collapses Put them together on content whose origin is *your* origin and the containment is gone. Same-origin plus scripting is precisely the condition under which one document may reach into another: ```html <!-- embedder at https://app.example --> <iframe id="embed" sandbox="allow-scripts allow-same-origin" src="https://app.example/widget"></iframe> ``` ```js // running inside https://app.example/widget const self = window.frameElement; // reachable: same origin as the parent self.removeAttribute('sandbox'); // the restrictions are declared by markup... self.src = self.src; // ...and re-applied only on load ``` The sandbox flags are computed when the frame **navigates**. Removing the attribute does not retroactively lift restrictions on the currently loaded document, but reloading the frame — by reassigning `src`, or by navigating itself — produces a fresh document that is created with no sandbox flags at all. From that moment the frame is an ordinary same-origin document: it can read `document.cookie` for your origin, read and write your `localStorage`, and script your page's DOM. Even without the escape trick, same-origin access alone already means the frame can do anything your own first-party script could do — the removal step just makes the point unmissable. ## The condition that actually matters The defect is not the token pair in the abstract; it is the token pair **plus shared origin**. Consider two embeds: - `https://app.example` framing `https://app.example/widget` with both tokens — broken. Same origin, so the frame is inside your trust boundary. - `https://app.example` framing `https://widget-sandbox.example/embed` with both tokens — meaningfully different. `allow-same-origin` restores *the widget's* origin, not yours. The frame gets its own cookies and storage, which is often exactly what a third-party embed legitimately needs, and it still cannot touch `app.example` because the same-origin check between the two documents fails. This is why the standard advice for hosting untrusted content — a user-authored HTML preview, a plugin surface, a rich-text render — is to give it a **separate origin**, not merely a separate path. A path is not an origin; `https://app.example/sandbox/` is the same origin as the rest of the site and buys you nothing. ## Choosing a configuration Work backwards from what the frame needs: - **Untrusted content, needs scripting, no session of its own:** `sandbox="allow-scripts"` and no `allow-same-origin`. This is the strongest useful configuration and is what code-playground-style previews want. Accept that the frame has no storage and no cookies. - **Third-party embed with its own login, served from its own origin:** both tokens are acceptable, because the origin separation carries the containment. Add only the extra tokens it demonstrably needs. - **Untrusted content you insist on serving from your own origin:** there is no safe token set. Move it to another origin, or do not frame it. ## What a good answer sounds like An interviewer is checking three things. First, that you can state the mechanism concretely — `frameElement`, `removeAttribute`, reload — rather than repeating "it's insecure". Second, that you name the *condition*: same origin as the embedder, because a candidate who says "never combine those tokens" is over-generalising and will block legitimate third-party embeds. Third, that your fix is origin separation rather than a cleverer token list, because no arrangement of tokens can re-establish a boundary that shared origin has already dissolved. One related check worth mentioning: the trick works because the attribute lives in the parent's markup and the frame can reach the parent's DOM. If the frame cannot reach the parent DOM — the cross-origin case — the attribute is unreachable and the escape does not exist.
- Is it always wrong to write sandbox="allow-scripts allow-same-origin"?No. It is wrong when the framed content is served from the embedder's own origin, because then the frame lands inside your trust boundary. For a third-party embed on its own origin, `allow-same-origin` restores *that* origin, which is what the embed needs for its own cookies and storage, and the same-origin check against your page still fails. The condition is shared origin, not the tokens themselves.
- If the frame removes the sandbox attribute, does it become unsandboxed immediately?No — the flags are fixed when the document is created. The currently loaded document keeps its restrictions; the escape completes on the next navigation of that frame, which the script can trigger itself by reassigning `src` or navigating. Practically it is a two-line escape, so the distinction is timing, not protection.
- You must preview untrusted user-authored HTML inside your app. What is the configuration?Serve the preview from a separate origin — a distinct hostname, not a path — and frame it with `sandbox="allow-scripts"`, omitting `allow-same-origin`. The preview can run its own code but has an opaque origin, no cookies, no storage, and no reach into your DOM. Add further tokens only against demonstrated breakage.
saying these in an interview costs you the question
- Says the pair is always forbidden, ignoring third-party origins
- Cannot name the escape mechanism beyond "it's insecure"
- Thinks a separate path counts as origin separation
- Believes removing the attribute has no effect at all
- Suggests fixing it with a longer token list instead of a separate origin