diff --git a/docs/contributing/openai-sdk-architecture.mdx b/docs/contributing/openai-sdk-architecture.mdx index 09db5153b..39c7c4569 100644 --- a/docs/contributing/openai-sdk-architecture.mdx +++ b/docs/contributing/openai-sdk-architecture.mdx @@ -251,7 +251,7 @@ sequenceDiagram #### Storage Implementation -Located in `server/routes/mcp/chatgpt.ts:14-51`: +Located in `server/routes/apps/chatgpt.ts`: ```typescript interface WidgetData { @@ -328,7 +328,7 @@ sequenceDiagram #### Sandbox Proxy Implementation -The middle iframe serves as a cross-origin relay between the host and widget. Located in `server/routes/mcp/chatgpt-sandbox-proxy.html`: +The middle iframe serves as a cross-origin relay between the host and widget. Located in `server/routes/apps/chatgpt-sandbox-proxy.html`: ```html @@ -476,7 +476,7 @@ graph LR #### API Implementation -Located in `server/routes/mcp/chatgpt.ts:213-376`: +Located in `server/routes/apps/chatgpt.ts`: **Core API Methods:** @@ -772,7 +772,7 @@ if (targetWindow) { The receiving view updates its local state and dispatches a `openai:widget_state` event: -Located in `server/routes/mcp/chatgpt.ts:410-428`: +Located in `server/routes/apps/chatgpt.ts`: ```javascript if ( @@ -983,7 +983,7 @@ sequenceDiagram **Implementation Details:** -1. **History Wrapping** (`server/routes/mcp/chatgpt.ts:352-407`): +1. **History Wrapping** (`server/routes/apps/chatgpt.ts`): ```javascript // Track navigation state @@ -1037,7 +1037,7 @@ case "openai:navigationStateChanged": { } ``` -3. **Navigation Command Handler** (`server/routes/mcp/chatgpt.ts:535-551`): +3. **Navigation Command Handler** (`server/routes/apps/chatgpt.ts`): ```javascript case 'openai:navigate': @@ -1391,7 +1391,7 @@ MCPJam Inspector implements configurable CSP enforcement for widget sandboxing w **CSP Modes:** - **Permissive** (default) - Allows all HTTPS resources for development and testing -- **Strict** (widget-declared) - Only allows domains declared in the widget's `openai/widgetCSP` metadata +- **Widget-declared** - Currently also permissive, allowing all HTTPS sources (widget CSP metadata not enforced) #### Widget CSP Declaration @@ -1421,22 +1421,26 @@ Widgets can declare required domains using the `openai/widgetCSP` metadata field #### CSP Header Generation -Located in `server/routes/mcp/chatgpt.ts:571-701`: +Located in `server/routes/apps/chatgpt.ts`: ```typescript function buildCspHeader( mode: CspMode, - widgetCsp?: WidgetCspMeta | null, + _widgetCsp?: WidgetCspMeta | null, ): CspConfig { - // Base trusted CDNs (always included for widget asset loading) - const baseTrustedCdns = [ - "https://persistent.oaistatic.com", - "https://*.oaistatic.com", - "https://unpkg.com", - "https://cdn.jsdelivr.net", - "https://cdnjs.cloudflare.com", - "https://cdn.skypack.dev", - "https://cdn.tailwindcss.com", + // Always allow localhost/127.* for widget development + sandbox proxy HMR + const localhostSources = [ + "http://localhost:*", + "http://127.0.0.1:*", + "https://localhost:*", + "https://127.0.0.1:*", + ]; + + // WebSocket sources for HMR, etc. + const wsSources = [ + "ws://localhost:*", + "ws://127.0.0.1:*", + "wss://localhost:*", ]; let connectDomains: string[]; @@ -1444,25 +1448,40 @@ function buildCspHeader( switch (mode) { case "permissive": - // Allow https: wildcard - connectDomains = ["'self'", "https:", "wss:", "ws:"]; + // Most lenient - allow https: wildcard + connectDomains = [ + "'self'", + "https:", + "wss:", + "ws:", + ...localhostSources, + ...wsSources, + ]; resourceDomains = [ "'self'", "data:", "blob:", "https:", - ...baseTrustedCdns, + ...localhostSources, ]; break; case "widget-declared": - // Honor widget's declared CSP - connectDomains = ["'self'", ...(widgetCsp?.connect_domains || [])]; + // Also permissive - allows all https: sources + connectDomains = [ + "'self'", + "https:", + "wss:", + "ws:", + ...localhostSources, + ...wsSources, + ]; resourceDomains = [ "'self'", "data:", "blob:", - ...(widgetCsp?.resource_domains || baseTrustedCdns), + "https:", + ...localhostSources, ]; break; } @@ -1474,11 +1493,11 @@ function buildCspHeader( "worker-src 'self' blob:", "child-src 'self' blob:", `style-src 'self' 'unsafe-inline' ${resourceDomains.join(" ")}`, - `img-src ${mode === "widget-declared" ? resourceDomains.join(" ") : "'self' data: blob: https:"}`, - `media-src ${mode === "widget-declared" ? resourceDomains.join(" ") : "'self' data: blob: https:"}`, + `img-src 'self' data: blob: https: ${localhostSources.join(" ")}`, + `media-src 'self' data: blob: https:`, `font-src 'self' data: ${resourceDomains.join(" ")}`, `connect-src ${connectDomains.join(" ")}`, - "frame-ancestors 'self'", + "frame-ancestors 'self' http://localhost:* http://127.0.0.1:* https://localhost:* https://127.0.0.1:*", ].join("; "); return { mode, connectDomains, resourceDomains, headerString }; @@ -1487,6 +1506,8 @@ function buildCspHeader( The CSP header is applied in the `/widget-content/:toolId` endpoint based on the `csp_mode` query parameter. +**Note:** As of PR #1059, both CSP modes are very permissive, allowing all `https:` sources. This simplifies widget development but reduces security isolation. Widget-declared CSP metadata is currently not enforced. + #### CSP Violation Reporting Widgets automatically report CSP violations to the parent for debugging. The injected widget script includes a violation listener: @@ -1535,8 +1556,8 @@ Located in `client/src/components/chat-v2/csp-debug-panel.tsx`. #### Development vs Production - **Development**: Use permissive mode to avoid CSP issues while building widgets -- **Testing**: Switch to strict mode in UI Playground to identify required domains -- **Production**: Always use strict mode with properly declared `openai/widgetCSP` metadata +- **Testing**: Both modes currently allow all HTTPS sources, simplifying widget development +- **Production**: Consider implementing stricter CSP enforcement based on widget metadata for enhanced security The `'unsafe-inline'` and `'unsafe-eval'` directives are currently required @@ -1954,8 +1975,9 @@ Remember that messages flow through the triple-iframe chain: - Restrict network access via CSP 4. **Content Security Policy:** + - Current implementation is very permissive (allows all `https:` sources) + - Consider implementing stricter CSP based on widget metadata for production - Remove `unsafe-eval` if possible (breaks some frameworks) - - Whitelist only trusted CDNs - Consider using nonces for inline scripts 5. **Audit Widget Code:** @@ -1971,9 +1993,9 @@ Remember that messages flow through the triple-iframe chain: - `client/src/components/ui/chatgpt-sandboxed-iframe.tsx` - Triple-iframe implementation with cross-origin isolation - `client/src/components/chat-v2/thread.tsx` - Manages PiP state across all widgets in the thread - `client/src/components/ChatTabV2.tsx` - Chat integration with transform isolation for z-index stacking -- `server/routes/mcp/chatgpt.ts` - Widget storage, serving, and OpenAI bridge injection -- `server/routes/mcp/chatgpt-sandbox-proxy.html` - Middle iframe proxy for cross-origin message relay -- `server/routes/mcp/index.ts` - Mounts ChatGPT routes at `/chatgpt` +- `server/routes/apps/chatgpt.ts` - Widget storage, serving, and OpenAI bridge injection +- `server/routes/apps/chatgpt-sandbox-proxy.html` - Middle iframe proxy for cross-origin message relay +- `server/routes/apps/index.ts` - Mounts ChatGPT routes at `/apps/chatgpt` - `client/src/components/chat-v2/csp-debug-panel.tsx` - CSP debug panel UI - `client/src/components/ui-playground/PlaygroundMain.tsx` - UI Playground with CSP mode selector - `client/src/stores/ui-playground-store.ts` - CSP mode state management