Skip to content

MCP Apps: declared frameDomains are received, but native guest still enforces a denying frame policy #5145

Closed

Description

@RobinLin666

Summary

A Copilot desktop MCP App cannot initialize an embedded Fabric Notebook. The UI resource declares the required origin in _meta.ui.csp.frameDomains, and the native resource bridge returns that metadata correctly. However, a fresh native guest reports an enforced frame-loading CSP violation whose applicable source list is 'none'.

Is there a supported configuration or approval step for enabling declared nested frames, or is this a host compatibility bug?

Related: #4271 reports opaque origins and inherited sandbox restrictions. This report adds an observed CSP denial on macOS.

Environment

  • GitHub Copilot desktop: 1.1.33, confirmed using get_app_info
  • OS: macOS
  • Installed release, not a local source build
  • MCP transport: local stdio
  • MCP Apps SDK: @modelcontextprotocol/ext-apps 2.0.3
  • UI resource MIME type: text/html;profile=mcp-app
  • Embedded application: Microsoft Fabric Notebook

Resource declaration

The server supplies this metadata both in resource discovery and on the relevant resources/read contents item:

_meta: {
  ui: {
    csp: {
      frameDomains: [
        "https://msit.powerbi.com",
        "https://daily.powerbi.com"
      ]
    }
  }
}

We verified that the Copilot native resource bridge returns the HTML with the expected declared origins, including https://msit.powerbi.com.

Observed errors and diagnostic details

1. Enforced frame-loading denial

A temporary listener registered before creating the nested iframe captured a frame-related SecurityPolicyViolationEvent:

disposition: enforce
blocked origin: https://msit.powerbi.com
applicable frame policy source list: 'none'

The App displayed the following application-generated diagnostic:

Host CSP blocked the Fabric iframe. Required origin: https://msit.powerbi.com.
Enable declared frame domains in the host; Notebook commands are disabled.

This is not a report-only warning. The fresh view did not render the Notebook or receive its expected Fabric-origin message.

Evidence limitation: the probe selected frame-src from originalPolicy, falling back to child-src and then default-src. It retained the source list but not the selected directive name or complete policy string. Therefore we can confirm an enforced frame denial, but cannot identify which directive or host component supplied it.

2. Opaque origin and unavailable storage

The native App also reported:

location.protocol: mcp-app-guest:
self.origin: null
isSecureContext: true
localStorage access: SecurityError

These observations indicate an opaque-origin environment. We did not directly inspect the native ancestor iframe sandbox attributes.

Reproduction flow

  1. Configure a local stdio MCP server exposing an MCP App resource and an open tool linked through _meta.ui.resourceUri.
  2. Declare the actual embed origin in _meta.ui.csp.frameDomains, in discovery and resources/read.
  3. Register a securitypolicyviolation listener before constructing the nested iframe.
  4. Open a fresh tool UI in Copilot and load the embedded application from the declared origin.
  5. Compare the returned resource metadata with the violation's disposition, blocked origin, and applicable frame-source policy.
  6. Inspect self.origin and whether accessing localStorage throws.

The observed reproduction uses an authenticated Fabric Notebook. Workspace and Notebook identifiers are omitted; a standalone public-domain minimal reproduction has not yet been verified.

Controlled comparisons

The same real Notebook successfully renders in a separate-origin browser AppBridge test host using:

sandbox="allow-scripts allow-same-origin"
  • Both Chromium and WebKit rendered the Notebook.
  • The browser-host test received successful acknowledgements for opening and closing the table of contents.
  • Chromium also rendered with non-secure HTTP ancestors, even when crypto.randomUUID and crypto.subtle were unavailable in the embedded page.

In a separate test, removing only allow-same-origin from the outer App iframe reproduced a missing-handshake symptom without a CSP violation. Chromium logged:

Failed to execute 'postMessage' on 'DOMWindow':
The target origin provided ('https://msit.powerbi.com')
does not match the recipient window's origin ('null').

This demonstrates a separate sandbox-origin compatibility blocker. It does not prove the exact native ancestor attributes or explain every earlier native failure.

Expected behavior and questions

The MCP Apps specification describes allowing nested frames from declared frameDomains, with frame-src 'none' used when no frame domains are provided:

https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/2026-01-26/apps.mdx#sandbox

Could maintainers clarify:

  1. Are declared nested frames supported for local stdio MCP Apps in Copilot desktop?
  2. Is any user, administrator, or per-server configuration/approval required?
  3. Could metadata propagation or policy caching explain a fresh guest enforcing a denial despite receiving frameDomains?
  4. What is the supported approach for embeds requiring real-origin semantics and browser storage, as discussed in MCP App sandbox omits allow-same-origin, so apps and their embeds run with a null origin #4271?
  5. Is there documentation for the native guest's actual CSP/sandbox restrictions and a supported macOS inspection workflow?

We want to follow the supported isolation model, not disable CSP or weaken postMessage origin checks.

Privacy and scope

No Copilot binaries or security settings were modified. Temporary probes were removed after testing. No Notebook cells were executed, edited, or saved. Credentials, workspace/Notebook identifiers, authenticated URLs, raw tool payloads, and full logs are omitted. Claude was not freshly probed in this investigation.

Activity

  1. self-assigned this
    on Nov 5, 2026
  2. chuwik commented on Nov 11, 2026

    @chuwik
    Contributor

    Fixed by github/github-app#25764, released in v1.2.0. In 1.1.33 the app parsed a completed tool call's UI resource twice and lost its declared CSP. A freshly mounted view then fell back to the restrictive default, frame-src 'none', so declared frameDomains were blocked. A regression test with frameDomains fails at v1.1.33 and passes at v1.2.0. Note that MCP Apps are currently a Dev-ring experiment while they're hardened (github/github-app#26726).

  3. bertrandgressier commented on Nov 12, 2026

    @bertrandgressier

    @chuwik thanks for noting that MCP Apps are currently a Dev-ring experiment. Two questions, unrelated to the frameDomains bug itself: (1) Is there a target release for MCP Apps in the stable ring? (2) Can a non-staff user opt in to the experiment (or the Dev ring) today? On 1.3.0 the app logs show mcp_apps_enabled: false and I can't find the setting. Details in #5705, also related: #1913.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions