Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 55 additions & 33 deletions docs/contributing/openai-sdk-architecture.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
<!doctype html>
Expand Down Expand Up @@ -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:**

Expand Down Expand Up @@ -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 (
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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':
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -1421,48 +1421,67 @@ 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[];
let resourceDomains: string[];

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;
}
Expand All @@ -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 };
Expand All @@ -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:
Expand Down Expand Up @@ -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

<Warning>
The `'unsafe-inline'` and `'unsafe-eval'` directives are currently required
Expand Down Expand Up @@ -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:**
Expand All @@ -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
Expand Down