From one odd nesting in ChatGPT's DOM to the full set of trade-offs behind isolating third-party UI
Why MCP and ChatGPT Apps Use Double Iframes — Frédéric Barthelet, Alpic · Frédéric Barthelet
20 min total·Actually worth watching closely: ~8 min·3 must-watch clips
- 0:15 – 2:30Listen
Why build MCP apps at all
Opens with the business case: the ChatGPT App Store and Claude connectors are becoming a new acquisition channel for businesses reaching consumer generalistic agents, and the value lies in being discovered inside the conversation and in getting to embed a piece of interactive UI.
The reason to build an MCP app isn't technical novelty — it's a new distribution channel.
This stretch is background; the screen is basically a title and a few product names, so listening is enough.▶ Jump to 0:15Speaker · Frédéric Barthelet - 2:30 – 3:58Skim
How a view actually gets rendered
Covers the view mechanism: the UI always appears as the result of a tool call, the server declares the matching UI in the tool's metadata, and the view itself is just a simple HTML document that can include CSS inline. Those declarations are already in the tool list at the start of the conversation, so the host can cache them ahead of time.
A view isn't a standalone page — it's one HTML document, declared in advance and hanging off a tool call's result.
The slides show the metadata structure and the call flow; a glance at the field names and the direction of the arrows is enough, no need to watch frame by frame.▶ Jump to 2:30Speaker · Frédéric Barthelet - 3:58 – 5:30Watch
The odd nesting, and content security policy enters
The speaker shows what he found while inspecting ChatGPT's page structure — the third-party view wrapped in two iframes — then introduces the prerequisite for understanding any of it: the host page's content security policy.
The security policy the host applies to itself is the real reason every later option gets ruled out.
The screen shows the real page structure; the two levels of nesting are obvious on sight and hard to assemble in your head from words alone.▶ Jump to 3:58Speaker · Frédéric Barthelet - 5:30 – 10:20Listen
Why direct injection doesn't work
Pushing third-party UI straight in through source doc is ruled out layer by layer: the host requires scripts to carry a one-time token generated per request, so third-party code never runs at all; loosen that and the inner frame shares the parent page's origin, exposing localStorage and cookies; add a sandbox and the origin turns opaque, so every storage API that depends on an origin stops working — and the only remedy hands the parent's origin straight back.
Origin isolation, sandboxing and usable storage can't all be had at once through direct injection.
This is a pure chain of elimination — each option ruled out raises the next problem — and the screen shows only scattered code snippets, so following the reasoning is the efficient way through.▶ Jump to 5:30Speaker · Frédéric Barthelet - 10:20 – 14:23Watch
The other route is blocked too, until the double design lands
Having the iframe load a remote URL fails as well: one domain per app makes the host's allowlist grow without limit as apps ship, and a single proxy domain works but means hosting unknown third-party code on your own domain — a liability and cost very few companies can carry. The answer: a dedicated outer domain loads a shared loader that creates the inner iframe and injects the content, with per-app subdomains keeping each app's storage apart.
The double nesting isn't over-engineering — it's the only point that satisfies isolation, scale and low cost at the same time.
The closing architecture diagram puts domain ownership across both layers, the script source and the subdomain split onto one slide; it's the densest picture in the talk.▶ Jump to 10:20Speaker · Frédéric Barthelet - 14:23 – 17:20Skim
Declaring a security policy for your own app
The spec lets an app bring its own security policy, delivered through a specific tag inside the outer iframe, with the declared domains rewritten into the inner one. Connecting to an external API to fetch data means listing that domain under the matching directive — scripts and network requests are the two that count.
If your view makes requests, the domain has to be declared first, or it will be blocked in production.
The slide maps the policy directives against the metadata fields; seeing where to write them and in what shape is enough, and the narration moves faster than the visuals.▶ Jump to 14:23Speaker · Frédéric Barthelet - 17:20 – 19:21Watch
SkyBridge tooling, live demo
A demo of Alpic's open source framework: execute any of your tools right in the browser, preview the associated view, and see your edits reflected live. Then the policy inspector, which compiles the domains you declared against the ones your view actually calls — a new API call added on the spot is flagged missing straight away, and turns green once declared.
The dev environment used to strip all security policies, so everything worked locally and only blew up in production — the point of this tooling is to pull that failure back onto your own machine.
It's hands-on throughout: edit code, watch the UI refresh, watch the check flip from red to green. The interaction is the content — you have to watch it to get a feel for using it.▶ Jump to 17:20Speaker · Frédéric Barthelet - 19:21 – 19:54Skim
Wrap-up and the on-stage raffle
Scan the codes to grab the slides and to try the framework, closing with a raffle for anyone who stars the open source repo on the spot.
If you want the materials, this is where to screenshot the QR code.
Just a QR code slide — skip it unless you want the links.▶ Jump to 19:21Speaker · Frédéric Barthelet