Skip to content

Scope embed sessions to their embed - #8071

Open
3mdistal wants to merge 5 commits into
mainfrom
mcp/scope-embed-sessions
Open

3mdistal wants to merge 5 commits into
mainfrom
mcp/scope-embed-sessions

Conversation

@3mdistal

@3mdistal 3mdistal commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

MCP Apps embed sessions now authorize the embedded target and its declared actions. create_embed_session is advertised and accepted only when the effective inline-apps signal is enabled. UI capabilities and UI approval proof require the signed-in browser cookie identity and same-origin evidence.

Ordinary inline cards reuse explicit mcp.directoryProfile.widgetTargets and read/write argument contracts. The signed session binds the app mount, target, resource IDs, and allowed actions/arguments; scoped renewal retains those limits. Cards without a contract return their ordinary full-app link. Embed target headers, referrers, and ambient browser cookies do not expand session authority. Approval-gated card calls return approval_required with guidance to finish in the full app.

An additive ticket-store migration records the issuing audience before redemption and renewal. Existing tickets without an audience require a fresh card call. Existing tables and columns remain intact.

Embed ticket minting, widget CSP and own-app resource metadata, start URLs, and audience checks use a configured, allowlisted forwarded public origin, otherwise the peer-validated request origin. Proxy deployments with internal request URLs need their public origin configured through APP_URL or the workspace gateway URL unless the proxy connects over loopback. Ordinary MCP metadata and reconstructed SDK requests retain their existing origin behavior. Originless/stdio calls retain their ordinary app link, and scoped renewal uses the issuing origin, including on beta and mounted apps. Originless local Design visual-edit calls still save their design/screens and return openUrl, but omit the short-lived editor handoff; completing that workflow requires the full app or an origin-bearing request.

Supported inline hosts still receive the one-time start URL and scoped-renewal metadata in _meta, because the card needs them. The narrowed session and authenticated renewal are the authority boundary. The inline signal narrows exposure and is the same deployment flag/email allowlist, trusted first-party header, or directory-mode signal used to render cards; a declared MCP client capability does not enable it.

Follow-up: align all MCP origin consumers, getOrigin, and getMcpOAuthAudiences on one allowlist-aware rule. This PR limits the origin change to embed sessions.

Card compatibility

This reduces the Mail draft, Calendar draft and Content sharing cards to full-app links for full-catalog callers where main showed them inline; scoped draft and document contracts follow in stacked PRs.

This inventory covers all 51 template actions on main that render embedApp cards, plus the three framework views. Inline rendering depends on the existing deployment flag/email allowlist, trusted first-party signal, or directory mode. Deployed production flag and allowlist values have not been verified. No per-card data contract is added here.

Result App Actions
Reuse existing contract Content create-document, create-content-database
Reuse existing contract Design create-design, create-design-from-template, generate-design, present-design-variants
Reuse existing contract Slides and Slides-2, each create-deck, add-slide
Full-app link Analytics get-analysis, compose-dashboard, ensure-demo-dashboards, ensure-risk-meeting-dashboard, ensure-native-v2-dashboards, get-sql-dashboard, open-traffic-dashboard, list-sql-dashboards, mutate-dashboard, save-analysis, reorder-dashboard-panels, update-dashboard
Full-app link Assets open-asset-picker, generate-asset
Full-app link Calendar manage-event-draft
Full-app link Clips get-recording-player-data, search-recordings
Full-app link Content share-local-file-document, submit-content-database-form
Full-app link Design add-localhost-screens, generate-screens, export-coding-handoff, show-design-questions, get-design-snapshot, open-visual-edit
Full-app link Forms create-form
Full-app link Mail manage-draft
Full-app link Plan visual-answer, update-visual-plan, import-visual-plan-source, create-visual-recap, create-visual-questions, create-visual-plan, create-ui-plan, create-prototype-plan, create-plan-design, convert-visual-plan-to-prototype
Full-app link Slides and Slides-2, each clone-context-slide, get-deck
Full-app link Framework view-screen, context-manifest-get, context-preview-get

Existing directory action/resource contracts remain in use. Their allowed data actions still work, subject to approval. Collaboration, presence, upload-status, and legacy upload endpoints are outside the action-runtime grant and stay denied; this PR does not claim full parity for those features. Metadata-only mcpApp declarations have no additional renderer to change.

Related open cards need bounded contracts in their owning PRs: #8005 and #8018 (Mail draft exposure/compact draft); #8017 (suggest-document-edit); and all seven action entries exposed by #8010 (Analytics traffic, Calendar draft, Content sharing, Design generation/handoff, Slides and Slides-2 clone). #8004 changes host documentation only. The owning thread has the declaration shape and fallback inventory. Either merge order is safe.

Browser callers and approval follow-up

Require a browser session for A2A approvals.

Approval predicates receive http for an unproven frontend label; bearer calls return approval_required, browser-cookie calls without a UI capability retain mint-and-retry, and execution caller labels remain unchanged.

getBrowserCookieSession and its private resolver started as a verbatim copy from #8016 at ab91ab94; this PR adds an explicit cookie-only custom-auth hook. #8016 owns the helper, and its thread will carry the matching update. Whichever PR lands second should rebase and drop the duplicate. #8016 is still open, so adding its review link to card approval_required responses remains a follow-up after it lands.

The shared browser client sends cookies and normal fetch metadata. Same-origin browser GETs commonly omit Origin, so Sec-Fetch-Site: same-origin remains valid origin evidence alongside the cookie identity; UI proof refuses when both headers are missing. Agent Native desktop loads app pages in Electron webviews using that browser client; no separate UI-capability caller was found. CSRF-only routes retain their existing missing-header and Tauri handling, so installed native apps, scripts, and server callers keep working without an Origin header. UI proof and capability minting require positive same-origin browser evidence alongside cookie identity, including in generated workers. An Origin header without a Host is also refused by the shared CSRF check. Ordinary server-side rendering skips capability minting. Dispatch's server-to-server calls use bearer/A2A credentials and do not mint UI capabilities. Clips desktop adds bearer authentication and omits cookies in development, so that path cannot mint a UI capability; gated operations require the full signed-in browser. Sandboxed card frames and direct Origin: null calls cannot mint or prove UI, and instead use their explicit scoped action grants. Over HTTPS the embed session cookie is Partitioned to the agent client's top-level site, so it isn't sent on a top-level visit to the app. In plain-HTTP local development the cookie is SameSite=Lax without partitioning. Expired or unrelated ambient embed cookies are ignored on normal app requests. Live scoped embed credentials and explicit embed markers still cannot mint a UI capability, and the cookie Max-Age is capped at the token's remaining lifetime. The capability handler checks live capability cookies directly, including trailing-slash and app-mounted routes. Ordinary session lookup validates embed origin only for signed embed credentials; empty or malformed forwarding does not interrupt normal browser-cookie sign-in. Invalid authority grants no embed session, while ticket minting, start and renewal retain explicit 400 errors and live scoped credentials still restrict UI proof. Static custom MCP App HTML remains available without an embed-session contract. Direct UI-mint test helpers now provide the matching Origin explicitly.

Custom authentication must explicitly configure the cookie-only AuthOptions.getBrowserSession hook for UI capabilities and browser approvals. Without it, UI-capability minting returns a 401 naming the hook, and the server warns once. Design uses its signed Builder identity cookie; Docs reads its existing anonymous cookie without creating a session. The embedded host adapter accepts and normalizes an explicitly configured browser hook. The changeset and authentication docs in all supported locales describe this migration.

Validation

  • Latest validation: 1,368 affected tests pass across 13 suites, including every inventoried H3-mocked suite reaching embed handling. Call-parity and catalog snapshots are unchanged. Coverage includes ordinary browser-cookie sessions with empty/malformed forwarding, strict ticket mint/start/renewal errors, forged/gateway/Netlify widget CSP and resource origins, retained scoped credentials and mixed-cookie UI refusal. The new regressions were checked before their corresponding fixes; no live server was used. Inline-disabled compact, full and app catalogs skip widget-origin validation. Calendar's 33 MCP-write tests pass after removing the stale ordinary-client helper expectation; the template MCP-spec sweep found no other such lists.
  • pnpm install, workspace postinstall builds, and recursive workspace typecheck pass. The root typecheck wrapper has a Windows spawn EINVAL; the equivalent recursive pnpm command completed successfully, with a final core typecheck after the resource-binding repair.
  • Focused MCP, embed-session, browser-cookie auth, UI capability, origin, action approval, and schema migration/upgrade/snapshot specs pass across the affected runs. Design's open-visual-edit suite passes all 48 specs. Refusal regressions were also checked against the pre-change source; no live server was used.
  • Current review-fix validation: 830 auth, action, embed-boundary, origin and A2A tests; 275 MCP server/profile tests; 23 Design, Docs and Analytics tests; and three generated-worker UI checks pass. The Analytics UI fixture uses a real framework login cookie in an in-memory database and keeps the bearer refusal. Core typecheck and build pass. The two stale CI assertions are updated. No live server or browser replay was used.
  • pnpm guards: all 93 checks pass with Git's GNU grep available on the Windows PATH. The settings guard also passes on the exact rebase base, b07c13bf20c4636931d3cf4d97c0d4ad8acdb305, in that environment. Both i18n guards pass. The coercion-ok annotation on the session matcher records the intentional invalid-origin non-match; ticket operations retain explicit errors.
  • The full deployment suite reports four path-related failures also reproduced with the pre-change source on Windows. The production-binding fixture passes in isolation. In the preceding validation round, all eight affected generated-worker checks passed with the built core dependency loaded natively by Node and the normal 30-second timeout; the default Windows Vite runner can exceed that timeout while loading the linked dist graph, before request execution. Linux CI remains the portability check.

@github-actions

Copy link
Copy Markdown
Contributor

Here's a visual recap of what changed:

Visual recap

Open the full interactive recap

builder-io-integration[bot]

This comment was marked as outdated.

builder-io-integration[bot]

This comment was marked as outdated.

builder-io-integration[bot]

This comment was marked as outdated.

3mdistal added a commit that referenced this pull request Oct 23, 2026
…kie-only hook, and redact a credential split from its scheme

- Carry #8071's AuthOptions.getBrowserSession hook in auth.ts byte for byte, so
  whichever PR merges second sees identical hunks.
- On the review page, an app with a custom getSession and no hook gets a page
  that says it can't confirm the sign-in, instead of a login page that only
  sends the reader back; its JSON and POST answer 401 naming the missing hook.
- Redact "Authorization: Bearer" or "Basic" with the credential on the next
  line; the bearer pass had nothing left to match once the scheme was gone.
- A request with an origin that doesn't parse no longer gets a configured
  loopback link.
builder-io-integration[bot]

This comment was marked as outdated.

@builder-io-integration builder-io-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Builder reviewed your changes — looks good ✅

Review Details

Incremental review summary

This commit separates embed-origin resolution from ordinary MCP request metadata, uses a validated origin for widget CSP and embed tickets, and adds coverage for malformed forwarded headers and proxy-origin cases. I reviewed the delta as a high-risk session/authorization change. The two prior findings are addressed: ordinary session lookup no longer parses embed origins when no embed credential is present, and widget CSP expansion now uses the same validated embed origin as ticket minting. The trailing-slash capability-cookie issue was resolved in the previous review. I found no additional confirmed, actionable issues in this incremental diff. The new malformed-origin paths fail closed rather than silently producing an unscoped session or widget.

🧪 Browser testing: Skipped — browser-test-planner is unavailable; these changes affect server-side session and origin validation, not visual presentation.

Copy link
Copy Markdown
Contributor

Thanks for the update. This PR changes the runtime human-approval boundary for A2A/action calls across core. Before approval, it needs an independent current-head approval from a verified BuilderIO member; the current review history shows only the integration bot.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants