Open-ended UI and MCP Apps

By the Syncfusion DevTutorials team · Last updated

A partner's MCP App arrives as full HTML. Host it in a sandboxed iframe with a CSP and message checks, so a malicious app cannot escape and a real one still works.

What Open-ended UI Is

Open-ended UI = someone else (another company's server, or a tool) sends a complete piece of interface, as HTML with scripts, and your app shows it.

In this lesson, you will host a courier's app inside the order-support app, and lock it down so a fake one cannot steal data, trick the user or approve a refund.

MCP App = an MCP extension where a tool returns a small web app as a resource, and the host shows it in a sandboxed iframe.

A courier offers a slot picker as an MCP App. It is handy: the customer picks a delivery time right inside the chat. But you do not control its HTML. A look-alike app can try to escape.

Press Play, or use Next to go one step at a time.

  • Frame 1: no sandbox. Every attempt works: the frame is part of your page.
  • Frame 2: allow-scripts allow-same-origin. Still almost no protection.
  • Frames 3 to 5: allow-scripts alone, then a CSP, then message checks. Each layer blocks more.

Simple lesson: open-ended UI is someone else's code in your app. Contain it.

A Real-World Example

Think about a visitor in an office building.

  • They get a badge that opens one meeting room, not every door.
  • They can't take files out of the building.
  • If they hand you a note "from the boss", you check it really is from the boss.

Hosting an MCP App works the same way:

  • One meeting room = the iframe sandbox.
  • No files out of the building = the Content Security Policy.
  • Checking the note = validating messages from the frame.

Click the button. The Build tab loads the look-alike app with no sandbox.

  • The Escape attempts box: each attempt, and whether it worked.
  • Side effects: what the fake app got away with.
  • Messages from the app: a fake "approve-refund" message.

Simple lesson: a frame without a sandbox is not a boundary.

What You Will Learn

In this lesson, you will learn:

  1. Why open-ended UI is the riskiest pattern.
  2. Which iframe sandbox flags contain untrusted HTML.
  3. Why two flags together undo the sandbox.
  4. How a CSP stops data leaving.
  5. How to check messages from the frame.

About the attacks in this lab

  • The six escape attempts are simulated with written-out rules that match how browsers treat iframe sandbox flags.
  • Real browsers enforce these rules themselves. Your job is to choose the right flags, and test your host in Chromium, Firefox and WebKit.
  • The AI Security course goes deeper into attacks like these. The MCP course covers how MCP Apps are served.

The Iframe Sandbox

Sandbox = an iframe attribute that turns off powers by default. Each flag you add turns one power back on.

sandbox valueScripts run?Can reach your page?Use it?
(none)YesYesNever, for untrusted HTML
allow-scripts allow-same-originYesYes, and it can remove its own sandboxNever, for untrusted HTML
allow-scriptsYesNo: it gets its own originYes
"" (empty)NoNoOnly for HTML with no scripts

The Dangerous Pair

allow-same-origin lets the frame keep your page's origin. With allow-scripts too, its script can reach your page, read cookies, and even take the sandbox off itself.

So for someone else's HTML, use allow-scripts on its own.

Too Tight Is Broken

An empty sandbox blocks scripts, so it blocks every attack. It also blocks the real app.

Step through both frames.

  • Frame 1: with no scripts, the courier's real slot picker is blank.
  • Frame 2: with allow-scripts, a CSP and message checks, the fake app is contained and the real one works.

Simple lesson: allow-scripts alone: the app runs, your page is out of reach.

The Content Security Policy

CSP = rules that say where a page may load from and send data to.

The sandbox keeps the frame away from your page. But the frame can still call any website and send what it collected. A CSP in the frame's HTML blocks that:

const csp = `<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data:">`;

<iframe title="Courier slot picker" sandbox="allow-scripts" srcDoc={csp + appHtml} />

default-src 'none' means: no network calls unless you allow them. Add only the origins the app really needs.

Check how your host serves the app.

  • Some hosts serve MCP Apps from a separate domain with their own CSP header.
  • Others use srcdoc, like the example above.
  • Read your host's docs, and test that a call to an unknown site really fails.

Simple lesson: the sandbox keeps it in; the CSP keeps your data in.

Checking Messages

The frame talks to your app with postMessage. A fake app can post { type: "approve-refund" } and hope your page believes it.

Step through both frames.

  • Frame 1: the fake app's message comes from the wrong origin, with an unknown type. Rejected.
  • Frame 2: the real app's slot-picked message comes from the courier's origin. Accepted.
window.addEventListener("message", (e) => {
  if (e.source !== frame.current?.contentWindow) return;   // only our frame
  if (!ALLOWED_TYPES.has(e.data?.type)) return;            // only known messages
  onMessage(e.data);
});

Never let a message from the frame trigger a risky action on its own. Show a preview and ask, as in Lesson 5.

Easy way to remember: Sandbox it, fence it, check its notes. allow-scripts, a CSP, and message checks.

Simple lesson: believe only known messages, from your own frame.

Lab Exercise: Lock Down the MCP App Host

Your task: change the Lesson 9 settings in the Build tab so both scenarios pass. Then press Check all scenarios.

Click the button and open the Build tab.

  • iframe sandbox: pick the value that runs the app but keeps your page out of reach.
  • CSP and message checks: turn both on.
  • Both scenarios: the fake app must be contained, and the real one must still work.

Simple lesson: contain the app without breaking it.

What Changes per Framework

OptionOpen-ended UI
Hand-built ReactAn iframe with sandbox="allow-scripts", a CSP and a message check, as above
MCP Apps SDKHost helpers for MCP App resources; still check its sandbox settings
CopilotKitShows MCP Apps carried over AG-UI
Claude and ChatGPTBoth hosts support MCP Apps for their own clients

Build It in React

In the Code tab, an mcp-app event brings the app's page as HTML. Your host decides what that page may do. Three lines do most of the work:

<iframe ref={frame} sandbox="allow-scripts" srcDoc={CSP + app.html} />
if (e.source !== frame.current?.contentWindow) return;   // only this frame
if (e.data?.type === "slot-picked") setSlot(e.data.slot);  // only known messages

The look-alike app in the second scenario really tries to read your page, send data out and approve a refund. The checks report which tries worked.

TL;DR

  1. Open-ended UI = someone else's HTML and scripts in your app. Highest risk.
  2. MCP App = a small web app from an MCP tool, shown in a sandboxed iframe.
  3. Sandbox = allow-scripts on its own. Never add allow-same-origin for untrusted HTML.
  4. CSP = stops the frame sending data out.
  5. Message check = only known types, only from your frame.
  6. Too tight = an empty sandbox breaks the real app.

Common mistakes

MistakeWrong ideaCorrect ideaWhat goes wrong
No sandbox"It's from a partner, so it's fine."Sandbox every third-party frame.A compromised partner reads your page.
allow-scripts with allow-same-origin"It needs both to work."Use allow-scripts alone.The frame removes its own sandbox.
No CSP"The sandbox covers it."Add a CSP so data can't leave.Collected data is sent to another site.
Trusting every message"Only our frame posts to us."Check the source and the type.A fake "approve-refund" is believed.
An empty sandbox"Safest is best."Allow scripts, contain the rest.The real app is blank.

Check Your Understanding

Exercises

What to Remember

  • Open-ended UI is someone else's code in your app.
  • Use sandbox="allow-scripts" on its own.
  • Never add allow-same-origin to untrusted HTML that runs scripts.
  • Add a CSP so data cannot leave.
  • Accept only known messages from your own frame.
  • Test your host in every browser you support.

Further Reading

Next: The AG-UI Channel, where you wire the agent and the UI over one event stream, with state updates that never erase what the user typed.