An iframe is only an isolation boundary if its origin and its sandbox flags make it one, and a srcdoc frame without a sandbox runs with the full privileges of the page that embeds it. Developers reach for srcdoc to render HTML they did not write: user-submitted markup, email bodies, rich previews, third-party widgets and, increasingly, HTML, SVG and scripts produced by large language models. If you have seen about:srcdoc in a browser address bar, error report or analytics feed, that is the URL every srcdoc document carries. This guide explains what the sandbox attribute restricts, how srcdoc documents get their origin, which token combinations quietly undo the protection, how to choose between a sandboxed srcdoc frame, a separate sandbox domain and a CSP sandbox header, and how to render AI output without opening an exfiltration channel. It is written for front-end engineers, application security teams and anyone shipping a product that displays generated content. Cross-window messaging is covered in the postMessage Security Guide; this page covers the frame itself.
Key takeaways
- An empty sandbox attribute applies every restriction. Each allow- token lifts one; start empty and add only what the content demonstrably needs.
- Never combine allow-scripts and allow-same-origin for same-origin content. The WHATWG HTML standard and MDN both warn that the embedded document can then remove its own sandbox attribute, leaving no protection at all.
- srcdoc content is same-origin unless sandboxed. Without the sandbox attribute (or with allow-same-origin), script in a srcdoc frame can read the parent's DOM, cookies and storage.
- Sandboxing does not stop data leaving. A sandboxed frame can still load images and make requests unless a Content Security Policy restricts it, which is exactly how rendered AI output becomes an exfiltration path.
- The strongest pattern is a separate origin. For hostile content, the standard advises serving it from a dedicated domain, so that even a user opening it directly cannot reach your application's origin.
What the sandbox attribute restricts
MDN describes the sandbox value as either empty, to apply all restrictions, or a space-separated list of tokens that lift particular restrictions. A sandboxed frame without allow-same-origin is treated as coming from an opaque origin: MDN states that such a resource always fails same-origin checks and its Origin is null, so it cannot read document.cookie or localStorage. Without allow-scripts, no script runs; without allow-forms, forms render but do not submit; without allow-popups, window.open and target="_blank" fail silently.
| Token | What it lifts | Risk when granted to untrusted content |
|---|---|---|
| allow-scripts | Script execution (not popups) | Moderate on its own; the frame can still make network requests and post messages |
| allow-same-origin | Keeps the real origin instead of an opaque one | High: gives access to that origin's cookies and storage; with allow-scripts on same-origin content, a full escape |
| allow-forms | Form submission | Phishing forms that post credentials to an attacker |
| allow-popups | window.open and target="_blank" | Opening attacker pages; popups inherit the sandbox unless the next token is set |
| allow-popups-to-escape-sandbox | Popups open without the sandbox flags | Intended for ads that link out; never for untrusted script |
| allow-top-navigation | Navigating the top-level page | High: redirect the whole tab to a phishing page |
| allow-top-navigation-by-user-activation | Top navigation only after a user gesture | Lower, but still a click-to-phish path |
| allow-top-navigation-to-custom-protocols | Navigation to non-http protocols | Launching registered protocol handlers |
| allow-modals | alert, confirm, print and prompt | Spoofed dialogs; denial of service |
| allow-downloads | Downloads through download links or navigation | Drive-by delivery of files that look like they came from you |
| allow-storage-access-by-user-activation | Storage Access API request for unpartitioned cookies | Cross-site cookie access after a gesture |
| allow-pointer-lock, allow-orientation-lock, allow-presentation | The named APIs | Low; grant only when needed |
Two other iframe attributes complete the picture. The allow attribute sets a Permissions Policy for the frame (camera, microphone, geolocation and others); MDN notes it further restricts the Permissions-Policy header and does not replace it. The referrerpolicy attribute controls what URL the frame's requests reveal; use no-referrer for untrusted content so the parent's path and query string are not leaked. The credentialless attribute loads the frame in a new, ephemeral context without the cookies and storage of its origin, which is useful for third-party embeds under Cross-Origin-Embedder-Policy.
srcdoc and about:srcdoc explained
- What it is: the srcdoc attribute holds the full HTML of the frame's document inline. The HTML standard defines about:srcdoc as a reserved, unresolvable URL used as the URL of every srcdoc document, which is why it appears in address bars, console messages, CSP reports and error trackers.
- Precedence: if both src and srcdoc are present, srcdoc wins. Leaving a src fallback does not make an unsafe srcdoc safe.
- Base URL: MDN states that about:srcdoc uses the embedding document's URL as its base URL when resolving relative links, so relative URLs in generated HTML point at your own site.
- Origin: an unsandboxed srcdoc document runs with the embedding page's origin. Add sandbox without allow-same-origin and it gets an opaque, null origin instead. This single attribute decides whether the frame is isolated.
- Escaping: srcdoc is an attribute, so the content must be attribute-escaped. The standard says to escape every ampersand and quotation mark, ampersands first, so that a quotation mark becomes " and not ". Setting the property from script (
frame.srcdoc = html) avoids hand-escaping, but the HTML itself is still untrusted. - CSP inheritance: a srcdoc document inherits the embedding page's Content Security Policy in current browsers, and you can tighten it further with a CSP meta element inside the srcdoc. A policy can only add restrictions, never remove inherited ones.
- Trusted Types: where Trusted Types are enforced, assignments to srcdoc are an injection sink and must go through a policy. See the Trusted Types and DOM XSS Prevention Guide.
Choosing an isolation pattern
| Pattern | How it isolates | Best for | Trade-offs |
|---|---|---|---|
| srcdoc with empty sandbox | Opaque origin, no scripts, no forms | Displaying static HTML such as emails or sanitised rich text | No interactivity; auto-height needs a parent-side approach |
| srcdoc with sandbox="allow-scripts" | Opaque origin, scripts run | Live previews, generated mini-apps, code playgrounds | Frame can still make network requests and post messages; needs a strict CSP |
| Separate sandbox domain (for example a registrable domain used only for user content) plus sandbox attribute | Different site and opaque origin | Hostile or user-uploaded content, AI artifacts that run code | Infrastructure and a second domain; the standard's recommendation for potentially hostile files |
| CSP sandbox response header | Sandboxes the document even when opened directly in a tab | User-uploaded HTML or SVG served from your own servers | MDN states it is ignored in meta and in report-only mode; header only |
| Content-Disposition: attachment | Browser downloads rather than renders | Files users upload but should not view inline | No in-page preview |
| Sanitise and render in the main DOM | No isolation; relies on the sanitiser | Simple formatted text | Any sanitiser bypass is XSS in your origin |
Decision guidance: if the content never needs script, use an empty sandbox and sanitise anyway. If it needs script and comes from users or a model, isolate it on a separate domain and sandbox it, because a sandboxed frame on your own origin is one misconfigured token away from an escape. Small teams without the capacity to run a second domain should use srcdoc with sandbox="allow-scripts" only, a strict inherited CSP and no allow-same-origin under any circumstance. Large platforms that render content from many tenants should use per-tenant or per-render subdomains so one tenant's content cannot share an origin with another's.
Rendering AI-generated content in iframes
Chat interfaces, coding assistants and agent consoles now render model output as HTML, SVG, Markdown with images, and runnable mini-applications. OWASP's Top 10 for LLM Applications 2025 lists this as LLM05 Improper Output Handling: model output must be treated like any other untrusted input. Prompt injection means the content may have been written by whoever controlled a document, email or web page the model read. See the OWASP LLM Top 10 LLM05 Improper Output Handling Guide.
- Isolate execution: run generated code only in a frame with sandbox="allow-scripts" and without allow-same-origin, preferably on a separate domain.
- Close the network: the sandbox does not stop requests. Give the frame a CSP with default-src 'none' and only the script and style sources it needs, and keep img-src and connect-src as narrow as possible. In the EchoLeak Microsoft 365 Copilot vulnerability, an image reference in a rendered answer carried sensitive data out, routed through Microsoft domains that the page's CSP trusted, with no user action needed.
- Do not trust allow-listed domains blindly: any domain that can redirect, proxy or preview URLs can carry data onwards. Prefer no external loads at all for generated content.
- Keep the bridge narrow: if the parent and frame must talk, define a small message schema, check event.source against the frame's contentWindow (the origin will be null) and never pass tokens or session data into the frame. The postMessage Security Guide covers the listener rules.
- Block navigation and forms: omit allow-top-navigation, allow-popups and allow-forms so generated content cannot redirect the user or harvest credentials.
- Keep secrets out: never inject API keys, OAuth tokens or agent credentials into generated HTML for convenience. A frame that can run script can read anything placed in it.
Headers that work with the frame
| Control | Set on | Purpose |
|---|---|---|
| Content-Security-Policy: frame-ancestors | Your pages | Decides who may embed you; does not fall back to default-src, and MDN states it is not supported in meta. OWASP ASVS 5.0 V3.4.6 requires it on every response |
| Content-Security-Policy: frame-src (or child-src) | The embedding page | Decides which origins your page may frame |
| Content-Security-Policy: sandbox | Untrusted documents you serve | Sandboxes the document even when opened directly; ASVS 5.0 V3.2.1 lists it as a control against rendering content in the wrong context |
| Permissions-Policy and the allow attribute | Embedding page and iframe | Denies powerful features to the frame |
| Referrer-Policy and referrerpolicy | Embedding page and iframe | Stops URLs with tokens or identifiers leaking to the frame |
| Cross-Origin-Opener-Policy | Your pages | Separates your window from popups the frame might open |
Framing your own pages by attackers is a separate threat (clickjacking, CAPEC-103). See the Clickjacking Guide and the Content Security Policy Guide.
How to implement
- Inventory frames. Search the codebase for iframe, srcdoc, sandbox and frame.srcdoc assignments, and list where each frame's content comes from (static, user, third party, model).
- Classify content trust. Anything from users, tenants, third parties or models is untrusted. Static first-party content can be framed without a sandbox.
- Set the strictest sandbox that works. Start every untrusted frame with an empty sandbox attribute, then add tokens one at a time with a written reason. Reject allow-same-origin with allow-scripts in code review.
- Add CSP for the frame. Rely on the inherited policy for srcdoc, add a stricter meta policy inside it, or serve the frame from a sandbox domain with its own header policy.
- Test in report-only first. Deploy the parent's CSP changes as Content-Security-Policy-Report-Only and collect violation reports (about:srcdoc in a report tells you which frame) before enforcing. Remember the CSP sandbox directive itself has no report-only mode.
- Pilot and roll back. Ship to an internal ring, watch errors and support tickets, then expand. Roll back by reverting the sandbox token change, not by removing the sandbox.
- Add tests. Write automated checks that assert sandbox values on every iframe and that the frame cannot read the parent's cookies.
Detection and measurement
- CSP violation reports: collect them centrally; blocked img-src or connect-src from a frame rendering generated content may indicate an exfiltration attempt.
- Static analysis: lint rules for iframe elements missing sandbox, and for the allow-scripts plus allow-same-origin pair.
- Egress monitoring: watch for long encoded query strings on image and fetch requests initiated from sandbox domains.
Metrics: percentage of untrusted-content frames with a sandbox attribute; number of frames granted allow-same-origin; number granted allow-top-navigation or allow-popups-to-escape-sandbox; share of generated-content frames served from a separate domain; CSP violations per thousand renders from content frames; time to remediate a lint finding.
Common failure patterns
- The self-removing sandbox: sandbox="allow-scripts allow-same-origin" on same-origin or srcdoc content, added to make a library work. The frame removes its own sandbox and runs as your origin.
- srcdoc without sandbox: developers assume srcdoc isolates content because it is "in a frame". It does not; it runs as the parent's origin.
- Sandbox without CSP: script is blocked, but images and links still reach external servers, so generated content can leak data in URLs, as the EchoLeak case showed.
- Escaping by hand: building srcdoc strings in server templates with incomplete attribute escaping, which lets content break out of the attribute into the parent page.
- Hostile files on the main domain: user-uploaded HTML or SVG is sandboxed when framed but served from your origin, so a direct link opens it unsandboxed. Serve it from a separate domain or with a CSP sandbox header.
- Wildcard messaging: a parent that accepts any message because the frame's origin is null, without checking event.source and the message schema.
Lessons from incidents
The EchoLeak case is the clearest public example of rendered AI output acting as a data channel. Link filtering removed ordinary Markdown links, but reference-style images got through, and the browser fetched them automatically through domains the CSP trusted. The lessons carry directly to iframe-rendered content: filter at the rendering layer, not only in the model's output; treat any allow-listed domain that redirects or previews URLs as an exit; and make the default for generated content no external loads at all.
Framework mapping
| Framework | Reference | Relevance |
|---|---|---|
| OWASP ASVS 5.0 | V3.2.1, V3.4.3, V3.4.6, V3.5.5, V15.2.5 | Content rendered in the wrong context, CSP, frame-ancestors, postMessage validation, sandboxing dangerous functionality |
| OWASP Top 10 for LLM Applications 2025 | LLM05 Improper Output Handling | Treat model output as untrusted before rendering |
| NIST SP 800-53 Rev. 5 | SC-18, SC-39, SI-10 | Mobile code, process isolation, input validation |
| NIST CSF 2.0 | PR.PS-06 | Secure development practices |
| MITRE CAPEC | CAPEC-103 Clickjacking | The framing threat in the other direction |
Where identity fits
Frames often sit next to sessions and tokens. A srcdoc frame with your origin can read session cookies that are not HttpOnly, tokens in localStorage and anything in the parent DOM. Keep access tokens and agent credentials out of any page area that renders untrusted content, and do not pass them through postMessage to content frames. See the OAuth for Browser-Based Applications and the BFF Pattern Guide for keeping tokens out of the browser.
Practitioner checklist
- Inventory every iframe and srcdoc use and classify its content source.
- Give every untrusted frame a sandbox attribute, starting empty.
- Block allow-scripts with allow-same-origin in code review and lint rules.
- Serve hostile or user-uploaded content from a separate domain or with a CSP sandbox header.
- Restrict network access from content frames with CSP, especially img-src and connect-src.
- Use no-referrer and a restrictive allow attribute on content frames.
- Validate event.source and schema on every message from a sandboxed frame.
- Route srcdoc assignments through Trusted Types where enforced.
- Collect CSP reports and track sandbox coverage metrics.
Standards and references
- WHATWG: HTML Living Standard, the iframe element
- WHATWG: HTML Living Standard, URLs and fetching (about:srcdoc)
- MDN: The iframe element
- MDN: CSP sandbox directive
- MDN: CSP frame-ancestors directive
- OWASP: Application Security Verification Standard 5.0
- OWASP: HTML5 Security Cheat Sheet
- OWASP: Top 10 for LLM Applications 2025
Related NHI Mgmt Group resources: postMessage Security Guide · Content Security Policy Guide · Cross-Site Scripting (XSS) Prevention Guide · OWASP LLM Top 10 LLM05 Improper Output Handling Guide