Skip to content

Address security and type safety issues in experimental messages implementation#181

Open
Copilot wants to merge 3 commits intofeat/experimental-messagesfrom
copilot/sub-pr-176
Open

Address security and type safety issues in experimental messages implementation#181
Copilot wants to merge 3 commits intofeat/experimental-messagesfrom
copilot/sub-pr-176

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 12, 2026

Addresses code review feedback on PR #176's experimental messages feature, fixing security vulnerabilities and type system limitations.

Security Hardening

sendExperimentalRequest now validates message sources and prevents resource leaks:

  • Validates event.source === window.parent to prevent spoofing
  • Adds configurable targetOrigin parameter (defaults to '*')
  • Implements 30s timeout with cleanup (configurable via options)
  • Supports AbortSignal for manual cancellation
  • Early validation of parent window availability
// New API supports timeout and cancellation
await sendExperimentalRequest('x/clipboard/write', 
  { text: 'hello' },
  { timeout: 5000, signal: abortController.signal }
);

Type System Improvements

onFallbackRequest return type changed from Promise<Record<string, unknown>> to Promise<unknown>:

  • Matches JSON-RPC spec (allows primitives, arrays, objects)
  • Supports MCP methods like sampling/createMessage without casting
  • Internal cast to AppResult maintains AppBridge compatibility

Test Improvements

  • Extracted createMockExtra() helper to eliminate repetitive mock object creation
  • Proper typing for RequestHandlerExtra instead of as never casts

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

- Add timeout/AbortSignal support to sendExperimentalRequest
- Validate event.source to prevent spoofing attacks
- Change targetOrigin from '*' to configurable parameter
- Update onFallbackRequest return type to Promise<unknown>
- Extract mockExtra helper in AppRenderer tests

Co-authored-by: idosal <[email protected]>
@idosal idosal marked this pull request as ready for review February 12, 2026 01:28
Copilot AI review requested due to automatic review settings February 12, 2026 01:28
- Move parent window check to start of function
- Reorder function declarations to avoid forward references
- Remove unnecessary blank line in test file

Co-authored-by: idosal <[email protected]>
Copilot AI changed the title [WIP] Add support for experimental messages in MCP Apps Address security and type safety issues in experimental messages implementation Feb 12, 2026
Copilot AI requested a review from idosal February 12, 2026 01:30
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds infrastructure for “experimental” JSON-RPC messages between guest iframes and the host AppRenderer, while tightening request handling and updating related typing/tests in the TypeScript SDKs.

Changes:

  • Extended sendExperimentalRequest with timeout and AbortSignal support plus listener cleanup.
  • Updated onFallbackRequest typing to return Promise<unknown> and refactored related test helpers.
  • Minor test cleanup in the web-component renderer test.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
sdks/typescript/server/src/index.ts Adds options (timeout/AbortSignal/targetOrigin) and cleanup logic to sendExperimentalRequest.
sdks/typescript/client/src/components/AppRenderer.tsx Widens onFallbackRequest return type and adjusts fallback handler wiring.
sdks/typescript/client/src/components/__tests__/AppRenderer.test.tsx Extracts a reusable createMockExtra() helper for fallback handler tests.
sdks/typescript/client/src/components/__tests__/UIResourceRendererWC.test.tsx Removes an eslint suppression but leaves a formatting artifact.

Comment on lines 220 to +244
@@ -228,20 +232,71 @@ let _experimentalRequestId = 0;
export function sendExperimentalRequest(
method: string,
params?: Record<string, unknown>,
options?: {
signal?: AbortSignal;
timeout?: number;
targetOrigin?: string;
},
): Promise<unknown> {
const id = ++_experimentalRequestId;
const timeout = options?.timeout ?? 30000;
const targetOrigin = options?.targetOrigin ?? '*';

Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

targetOrigin currently defaults to '*' (and the JSDoc documents that default). For postMessage-based RPC this is an unsafe default because it allows sending requests to any origin. Prefer requiring an explicit targetOrigin (or deriving it from a trusted source like the embedding origin) and update the docs accordingly.

Copilot uses AI. Check for mistakes.
Comment on lines +257 to 262
}

const data = event.data;
if (data?.jsonrpc === '2.0' && data?.id === id) {
window.removeEventListener('message', handler);
cleanup();
if (data.error) {
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

The message handler validates event.source but does not validate event.origin. Without checking the expected origin, a compromised/untrusted embedding page (or an unexpected parent origin) could spoof responses for in-flight request IDs. Add an origin check tied to a configured/derived expected origin before accepting the response.

Copilot uses AI. Check for mistakes.
Comment on lines 396 to 400
// not yet in the Apps spec like "sampling/createMessage")
bridge.fallbackRequestHandler = async (request, extra) => {
if (onFallbackRequestRef.current) {
return onFallbackRequestRef.current(request, extra);
return onFallbackRequestRef.current(request, extra) as Promise<AppResult>;
}
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

onFallbackRequest is typed as Promise<unknown>, but the result is cast to Promise<AppResult> when wiring bridge.fallbackRequestHandler. This cast bypasses type safety and makes it easy to return a value that AppBridge/transport can’t handle. Consider changing onFallbackRequest to return Promise<AppResult> (or narrowing/validating the returned value) and removing the cast.

Copilot uses AI. Check for mistakes.
@@ -141,7 +141,6 @@ describe('UIResourceRendererWC', () => {
const el = document.createElement('ui-resource-renderer');

Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

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

There’s a stray whitespace-only line here (line 144) after removing the eslint-disable comment. Please delete it to avoid trailing-whitespace noise and keep the test formatted consistently.

Suggested change

Copilot uses AI. Check for mistakes.
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.

3 participants