Skip to content

feat: Add ChatGPT App sample with MoEngage integration#40

Open
Aman-engg-sdk wants to merge 6 commits into
masterfrom
feat/chatgpt-app-integration
Open

feat: Add ChatGPT App sample with MoEngage integration#40
Aman-engg-sdk wants to merge 6 commits into
masterfrom
feat/chatgpt-app-integration

Conversation

@Aman-engg-sdk
Copy link
Copy Markdown
Contributor

Overview

This PR adds a new sample application demonstrating MoEngage WebSDK integration with ChatGPT Apps.

Features

  • ✅ Next.js 14 app with TypeScript
  • ✅ Full MoEngage WebSDK integration
  • ✅ ChatGPT Apps SDK integration
  • ✅ AI bot detection and blocking
  • ✅ AI-assisted browser detection
  • ✅ GPT app environment detection
  • ✅ Comprehensive event tracking
  • ✅ User identification and session management
  • ✅ Complete documentation and testing guides

What's Included

  • Complete Next.js application in chatgpt-app-sample/ folder
  • React components for UI (UserIdentification, EventTracking, AIDetection, etc.)
  • Custom hooks for MoEngage, AI detection, and tracking
  • Library utilities for MoEngage SDK, AI detection, and ChatGPT Apps
  • Comprehensive documentation:
    • README.md - Overview and quick start
    • QUICKSTART.md - 5-minute setup guide
    • TESTING_GUIDE.md - Detailed testing instructions
    • DEPLOYMENT_INSTRUCTIONS.md - Deployment steps
    • INTEGRATION_EXAMPLES.md - Code examples

Testing

  • Local testing: npm install && npm run dev
  • Runs on port 4000 by default
  • Full MoEngage tracking integration
  • ChatGPT Apps compatibility

Documentation

All documentation is included in the chatgpt-app-sample/ folder. See README.md for quick start.

- Next.js 14 app with TypeScript
- Full MoEngage WebSDK integration
- ChatGPT Apps SDK integration
- AI bot detection and blocking
- AI-assisted browser detection
- Comprehensive event tracking
- User identification and session management
- Complete documentation and testing guides
- .next is a build artifact and should only exist inside chatgpt-app-sample when building
- Already properly gitignored in chatgpt-app-sample/.gitignore
- All project files should be contained within chatgpt-app-sample directory
- next-env.d.ts is Next.js TypeScript definitions file
- vercel.json is deployment configuration
if (typeof window === 'undefined') return false;

const isGPTApp =
window.location.href.includes('chat.openai.com') ||

Check failure

Code scanning / CodeQL

Incomplete URL substring sanitization High

'
chat.openai.com
' can be anywhere in the URL, and arbitrary hosts may come before or after it.

Copilot Autofix

AI 5 months ago

How to fix the problem in general:
Instead of using a substring check on the entire URL, parse the URL to extract the hostname and compare it against a whitelist of allowed hostnames. This prevents false positives from matches in the query string, path, or other portions of the URL.

Detailed best fix for the shown code:

  • Use the browser's built-in URL class to parse window.location.href and (for parent frame) window.parent.location.href, then inspect their .hostname property.
  • Compare against an explicit allowlist of hostnames, e.g., ['chat.openai.com', 'chatgpt.com'].
  • Apply this same logic for both the current window and parent frame checks.
  • You can use hostname.endsWith(...) if you want to support subdomains too (cautiously), but to faithfully match the original intention, strict equality is appropriate.

Where to apply changes:

  • Lines 124, 125: Replace substring checks on window.location.href.
  • Lines 128-129: Similarly, replace substring checks on window.parent.location.href.

Imports/definitions needed:

  • Use the native URL object (well supported on all modern browsers, no import needed).

Suggested changeset 1
chatgpt-app-sample/lib/ai-detection.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/chatgpt-app-sample/lib/ai-detection.ts b/chatgpt-app-sample/lib/ai-detection.ts
--- a/chatgpt-app-sample/lib/ai-detection.ts
+++ b/chatgpt-app-sample/lib/ai-detection.ts
@@ -120,13 +120,24 @@
   detectGPTApp(): boolean {
     if (typeof window === 'undefined') return false;
 
+    const allowedHostnames = ['chat.openai.com', 'chatgpt.com'];
+    let currentHostname: string, parentHostname: string | null = null;
+    try {
+      currentHostname = new URL(window.location.href).hostname;
+    } catch {
+      currentHostname = '';
+    }
+    if (window.parent !== window) {
+      try {
+        parentHostname = new URL(window.parent.location.href).hostname;
+      } catch {
+        parentHostname = null;
+      }
+    }
     const isGPTApp =
-      window.location.href.includes('chat.openai.com') ||
-      window.location.href.includes('chatgpt.com') ||
-      (window as any).__OPENAI_APPS__ !== undefined ||
-      (window.parent !== window &&
-        (window.parent.location.href.includes('chat.openai.com') ||
-          window.parent.location.href.includes('chatgpt.com')));
+      allowedHostnames.includes(currentHostname) ||
+      ((window as any).__OPENAI_APPS__ !== undefined) ||
+      (parentHostname !== null && allowedHostnames.includes(parentHostname));
 
     this.detectionResults.isGPTApp = isGPTApp;
     this.detectionResults.detectionDetails.gptAppDetection = {
EOF
@@ -120,13 +120,24 @@
detectGPTApp(): boolean {
if (typeof window === 'undefined') return false;

const allowedHostnames = ['chat.openai.com', 'chatgpt.com'];
let currentHostname: string, parentHostname: string | null = null;
try {
currentHostname = new URL(window.location.href).hostname;
} catch {
currentHostname = '';
}
if (window.parent !== window) {
try {
parentHostname = new URL(window.parent.location.href).hostname;
} catch {
parentHostname = null;
}
}
const isGPTApp =
window.location.href.includes('chat.openai.com') ||
window.location.href.includes('chatgpt.com') ||
(window as any).__OPENAI_APPS__ !== undefined ||
(window.parent !== window &&
(window.parent.location.href.includes('chat.openai.com') ||
window.parent.location.href.includes('chatgpt.com')));
allowedHostnames.includes(currentHostname) ||
((window as any).__OPENAI_APPS__ !== undefined) ||
(parentHostname !== null && allowedHostnames.includes(parentHostname));

this.detectionResults.isGPTApp = isGPTApp;
this.detectionResults.detectionDetails.gptAppDetection = {
Copilot is powered by AI and may make mistakes. Always verify output.

const isGPTApp =
window.location.href.includes('chat.openai.com') ||
window.location.href.includes('chatgpt.com') ||

Check failure

Code scanning / CodeQL

Incomplete URL substring sanitization High

'
chatgpt.com
' can be anywhere in the URL, and arbitrary hosts may come before or after it.

Copilot Autofix

AI 5 months ago

General fix:
Instead of performing a substring check on the full URL (which is vulnerable to bypasses and false positives), parse the current page's URL and match the hostname property against an explicit allowlist containing 'chat.openai.com', 'chatgpt.com', and their canonical subdomains. This also applies to the iframe parent domain check.

Detailed steps to fix:

  • Parse the current location URL (and window.parent.location.href if checking the parent) using the built-in URL class.
  • Safely compare the hostname property to an allow-list, e.g., ['chat.openai.com', 'chatgpt.com', 'www.chatgpt.com'].
  • Do not use .includes() on the entire URL string.
  • Ensure to handle exceptions in case the URL constructor fails (e.g., malformed URL in a hostile iframe), or the host is unavailable due to cross-origin restrictions.

Code Regions to Change:

  • Replace the substring checks on lines 124 and 125 with explicit checks of the parsed hostname.
  • Similarly, update the check in the iframe branch (lines 127–129) to parse and check the parent's location hostname safely.

Implementation considerations:

  • Import or define an allow-list of valid GPT app hostnames.
  • Account for window.parent.location possibly throwing if cross-origin (wrap in try/catch).
  • No new dependencies needed, only standard JavaScript API.

Suggested changeset 1
chatgpt-app-sample/lib/ai-detection.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/chatgpt-app-sample/lib/ai-detection.ts b/chatgpt-app-sample/lib/ai-detection.ts
--- a/chatgpt-app-sample/lib/ai-detection.ts
+++ b/chatgpt-app-sample/lib/ai-detection.ts
@@ -120,13 +120,27 @@
   detectGPTApp(): boolean {
     if (typeof window === 'undefined') return false;
 
+    const allowedHosts = ['chat.openai.com', 'chatgpt.com', 'www.chatgpt.com'];
+    function isAllowedHost(urlStr: string | null): boolean {
+      if (!urlStr) return false;
+      try {
+        const host = new URL(urlStr).hostname;
+        return allowedHosts.includes(host);
+      } catch (e) {
+        return false;
+      }
+    }
+
     const isGPTApp =
-      window.location.href.includes('chat.openai.com') ||
-      window.location.href.includes('chatgpt.com') ||
+      isAllowedHost(window.location.href) ||
       (window as any).__OPENAI_APPS__ !== undefined ||
-      (window.parent !== window &&
-        (window.parent.location.href.includes('chat.openai.com') ||
-          window.parent.location.href.includes('chatgpt.com')));
+      (window.parent !== window && (() => {
+        try {
+          return isAllowedHost(window.parent.location.href);
+        } catch (e) {
+          return false;
+        }
+      })());
 
     this.detectionResults.isGPTApp = isGPTApp;
     this.detectionResults.detectionDetails.gptAppDetection = {
EOF
@@ -120,13 +120,27 @@
detectGPTApp(): boolean {
if (typeof window === 'undefined') return false;

const allowedHosts = ['chat.openai.com', 'chatgpt.com', 'www.chatgpt.com'];
function isAllowedHost(urlStr: string | null): boolean {
if (!urlStr) return false;
try {
const host = new URL(urlStr).hostname;
return allowedHosts.includes(host);
} catch (e) {
return false;
}
}

const isGPTApp =
window.location.href.includes('chat.openai.com') ||
window.location.href.includes('chatgpt.com') ||
isAllowedHost(window.location.href) ||
(window as any).__OPENAI_APPS__ !== undefined ||
(window.parent !== window &&
(window.parent.location.href.includes('chat.openai.com') ||
window.parent.location.href.includes('chatgpt.com')));
(window.parent !== window && (() => {
try {
return isAllowedHost(window.parent.location.href);
} catch (e) {
return false;
}
})());

this.detectionResults.isGPTApp = isGPTApp;
this.detectionResults.detectionDetails.gptAppDetection = {
Copilot is powered by AI and may make mistakes. Always verify output.
window.location.href.includes('chatgpt.com') ||
(window as any).__OPENAI_APPS__ !== undefined ||
(window.parent !== window &&
(window.parent.location.href.includes('chat.openai.com') ||

Check failure

Code scanning / CodeQL

Incomplete URL substring sanitization High

'
chat.openai.com
' can be anywhere in the URL, and arbitrary hosts may come before or after it.

Copilot Autofix

AI 5 months ago

The bug: The code currently checks for the presence of "chat.openai.com" and "chatgpt.com" as substrings anywhere in window.location.href and (if in an iframe) window.parent.location.href. Instead, the hostname part of the URL should be parsed and compared strictly against allowed hostnames or their subdomains.

How to fix:

  • Replace all uses of .includes('chat.openai.com') and .includes('chatgpt.com') with proper hostname checks using the URL API (new URL(...)). Extract the hostname and check if it matches the allowed names.
  • To avoid breaking sites served from subdomains (like beta.chat.openai.com), allow either exact matches or specific subdomains (optionally using a helper function).
  • For iframe detection, wrap any window.parent.location.href usage in a try/catch to handle cross-origin errors.
  • Only change the detectGPTApp() method as shown.

What is needed:

  • Use the standard URL class for parsing.
  • Add a helper function to validate hostnames (either exact match or subdomain).
  • Wrap access to window.parent.location.href with try/catch.
Suggested changeset 1
chatgpt-app-sample/lib/ai-detection.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/chatgpt-app-sample/lib/ai-detection.ts b/chatgpt-app-sample/lib/ai-detection.ts
--- a/chatgpt-app-sample/lib/ai-detection.ts
+++ b/chatgpt-app-sample/lib/ai-detection.ts
@@ -120,19 +120,46 @@
   detectGPTApp(): boolean {
     if (typeof window === 'undefined') return false;
 
-    const isGPTApp =
-      window.location.href.includes('chat.openai.com') ||
-      window.location.href.includes('chatgpt.com') ||
-      (window as any).__OPENAI_APPS__ !== undefined ||
-      (window.parent !== window &&
-        (window.parent.location.href.includes('chat.openai.com') ||
-          window.parent.location.href.includes('chatgpt.com')));
+    function isAllowedHost(hostname: string): boolean {
+      const allowed = ['chat.openai.com', 'chatgpt.com'];
+      // Check for exact match, or direct subdomains of these hosts
+      return allowed.some(
+        (h) => hostname === h || hostname.endsWith('.' + h)
+      );
+    }
 
+    let isGPTApp = false;
+    try {
+      const pageHost = new URL(window.location.href).hostname;
+      isGPTApp =
+        isAllowedHost(pageHost) ||
+        (window as any).__OPENAI_APPS__ !== undefined;
+    } catch (e) {
+      // Ignore invalid URL
+    }
+
+    // Check parent frame if exists and not the same window
+    let inIframe = false;
+    let parentUrl: string | null = null;
+    if (window.parent !== window) {
+      inIframe = true;
+      try {
+        const purl = window.parent.location.href;
+        parentUrl = purl;
+        const parentHost = new URL(purl).hostname;
+        if (isAllowedHost(parentHost)) {
+          isGPTApp = true;
+        }
+      } catch (e) {
+        // Can't access parent URL due to cross-origin; leave parentUrl as null
+      }
+    }
+
     this.detectionResults.isGPTApp = isGPTApp;
     this.detectionResults.detectionDetails.gptAppDetection = {
       detected: isGPTApp,
-      inIframe: window.parent !== window,
-      parentUrl: window.parent !== window ? window.parent.location.href : null,
+      inIframe: inIframe,
+      parentUrl: parentUrl,
     };
 
     return isGPTApp;
EOF
@@ -120,19 +120,46 @@
detectGPTApp(): boolean {
if (typeof window === 'undefined') return false;

const isGPTApp =
window.location.href.includes('chat.openai.com') ||
window.location.href.includes('chatgpt.com') ||
(window as any).__OPENAI_APPS__ !== undefined ||
(window.parent !== window &&
(window.parent.location.href.includes('chat.openai.com') ||
window.parent.location.href.includes('chatgpt.com')));
function isAllowedHost(hostname: string): boolean {
const allowed = ['chat.openai.com', 'chatgpt.com'];
// Check for exact match, or direct subdomains of these hosts
return allowed.some(
(h) => hostname === h || hostname.endsWith('.' + h)
);
}

let isGPTApp = false;
try {
const pageHost = new URL(window.location.href).hostname;
isGPTApp =
isAllowedHost(pageHost) ||
(window as any).__OPENAI_APPS__ !== undefined;
} catch (e) {
// Ignore invalid URL
}

// Check parent frame if exists and not the same window
let inIframe = false;
let parentUrl: string | null = null;
if (window.parent !== window) {
inIframe = true;
try {
const purl = window.parent.location.href;
parentUrl = purl;
const parentHost = new URL(purl).hostname;
if (isAllowedHost(parentHost)) {
isGPTApp = true;
}
} catch (e) {
// Can't access parent URL due to cross-origin; leave parentUrl as null
}
}

this.detectionResults.isGPTApp = isGPTApp;
this.detectionResults.detectionDetails.gptAppDetection = {
detected: isGPTApp,
inIframe: window.parent !== window,
parentUrl: window.parent !== window ? window.parent.location.href : null,
inIframe: inIframe,
parentUrl: parentUrl,
};

return isGPTApp;
Copilot is powered by AI and may make mistakes. Always verify output.
(window as any).__OPENAI_APPS__ !== undefined ||
(window.parent !== window &&
(window.parent.location.href.includes('chat.openai.com') ||
window.parent.location.href.includes('chatgpt.com')));

Check failure

Code scanning / CodeQL

Incomplete URL substring sanitization High

'
chatgpt.com
' can be anywhere in the URL, and arbitrary hosts may come before or after it.

Copilot Autofix

AI 5 months ago

To securely detect whether the current or parent window is hosted on "chat.openai.com" or "chatgpt.com", the code should parse the relevant URL and examine the hostname directly. This way, it only matches exact allowed hostnames (like 'chat.openai.com' or 'chatgpt.com') and not any arbitrary host or embedded path/query. The best fix is to define a list of allowed hosts, parse the hostname from window.location.href and window.parent.location.href, and check for exact matches (or, if appropriate, check if the hostname ends with ".chatgpt.com" or similar, after validating for subdomains).

Changes needed:

  • At the start of detectGPTApp, define a whitelist of allowed hostnames, e.g. ['chat.openai.com', 'chatgpt.com'].
  • For both the current window and its parent (if not same as self), parse the URLs using the standard URL constructor and check if .hostname equals one of the allowed hosts.
  • Fallback to the previous check for the special case of __OPENAI_APPS__ as in current logic.

Requires:

  • Import or usage of URL (native global constructor in modern browsers).
  • Updating all substring matches in detectGPTApp (within .href.includes('...')) to correctly parse and check hostname instead.

No external dependencies are needed; the standard URL class suffices.


Suggested changeset 1
chatgpt-app-sample/lib/ai-detection.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/chatgpt-app-sample/lib/ai-detection.ts b/chatgpt-app-sample/lib/ai-detection.ts
--- a/chatgpt-app-sample/lib/ai-detection.ts
+++ b/chatgpt-app-sample/lib/ai-detection.ts
@@ -120,13 +120,21 @@
   detectGPTApp(): boolean {
     if (typeof window === 'undefined') return false;
 
+    const allowedHosts = ['chat.openai.com', 'chatgpt.com'];
+    let currentHost = '';
+    let parentHost = '';
+    try {
+      currentHost = new URL(window.location.href).hostname;
+    } catch {}
+    if (window.parent !== window) {
+      try {
+        parentHost = new URL(window.parent.location.href).hostname;
+      } catch {}
+    }
     const isGPTApp =
-      window.location.href.includes('chat.openai.com') ||
-      window.location.href.includes('chatgpt.com') ||
-      (window as any).__OPENAI_APPS__ !== undefined ||
-      (window.parent !== window &&
-        (window.parent.location.href.includes('chat.openai.com') ||
-          window.parent.location.href.includes('chatgpt.com')));
+      allowedHosts.includes(currentHost) ||
+      ((window as any).__OPENAI_APPS__ !== undefined) ||
+      (window.parent !== window && allowedHosts.includes(parentHost));
 
     this.detectionResults.isGPTApp = isGPTApp;
     this.detectionResults.detectionDetails.gptAppDetection = {
EOF
@@ -120,13 +120,21 @@
detectGPTApp(): boolean {
if (typeof window === 'undefined') return false;

const allowedHosts = ['chat.openai.com', 'chatgpt.com'];
let currentHost = '';
let parentHost = '';
try {
currentHost = new URL(window.location.href).hostname;
} catch {}
if (window.parent !== window) {
try {
parentHost = new URL(window.parent.location.href).hostname;
} catch {}
}
const isGPTApp =
window.location.href.includes('chat.openai.com') ||
window.location.href.includes('chatgpt.com') ||
(window as any).__OPENAI_APPS__ !== undefined ||
(window.parent !== window &&
(window.parent.location.href.includes('chat.openai.com') ||
window.parent.location.href.includes('chatgpt.com')));
allowedHosts.includes(currentHost) ||
((window as any).__OPENAI_APPS__ !== undefined) ||
(window.parent !== window && allowedHosts.includes(parentHost));

this.detectionResults.isGPTApp = isGPTApp;
this.detectionResults.detectionDetails.gptAppDetection = {
Copilot is powered by AI and may make mistakes. Always verify output.
if (typeof window === 'undefined') return false;

return (
window.location.href.includes('chat.openai.com') ||

Check failure

Code scanning / CodeQL

Incomplete URL substring sanitization High

'
chat.openai.com
' can be anywhere in the URL, and arbitrary hosts may come before or after it.

Copilot Autofix

AI 5 months ago

To correctly check if the current page or its parent is running on an allowed host (either chat.openai.com, chatgpt.com, or an explicitly whitelisted subdomain thereof), we should:

  1. Parse the URL using the URL constructor to extract the hostname field, which is guaranteed to be the authority portion (host) of the URL and not contain parts of the pathname or query.
  2. Check the hostname against an explicit whitelist: chat.openai.com, www.chatgpt.com, chatgpt.com, and any other valid subdomains, as appropriate.
  3. For extensibility:
    • It is usually safer to require an exact match or a subdomain match (e.g., something.chat.openai.com but not chat.openai.com.evil.com). This is achieved by checking for equality or for endsWith('.allowed.com').

Implementation:

  • In the two places in isChatGPTApp where window.location.href.includes(...) is used, replace with checked .hostname using a URL object.
  • Do the same for window.parent.location.href, but wrap in a try/catch since cross-origin access to window.parent.location may throw.
  • Define a whitelist array (e.g., const allowedHosts).
  • Replace all substring checks with exact/endsWith checks on the hostname.

No new dependencies or uncommon APIs are needed (URL is native).

Suggested changeset 1
chatgpt-app-sample/lib/chatgpt-apps.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/chatgpt-app-sample/lib/chatgpt-apps.ts b/chatgpt-app-sample/lib/chatgpt-apps.ts
--- a/chatgpt-app-sample/lib/chatgpt-apps.ts
+++ b/chatgpt-app-sample/lib/chatgpt-apps.ts
@@ -33,13 +33,35 @@
 export const isChatGPTApp = (): boolean => {
   if (typeof window === 'undefined') return false;
 
+  const allowedHosts = [
+    'chat.openai.com',
+    'chatgpt.com',
+    'www.chatgpt.com'
+  ];
+
+  // Helper to check if a given location belongs to an allowed host
+  const isAllowedHost = (loc: Location): boolean => {
+    try {
+      const { hostname } = new URL(loc.href);
+      return allowedHosts.includes(hostname);
+    } catch {
+      return false;
+    }
+  };
+
   return (
-    window.location.href.includes('chat.openai.com') ||
-    window.location.href.includes('chatgpt.com') ||
+    isAllowedHost(window.location) ||
     window.__OPENAI_APPS__ !== undefined ||
     (window.parent !== window &&
-      (window.parent.location.href.includes('chat.openai.com') ||
-        window.parent.location.href.includes('chatgpt.com')))
+      // Cross-origin access to window.parent.location may throw, so wrap in try/catch.
+      (() => {
+        try {
+          return isAllowedHost(window.parent.location);
+        } catch {
+          return false;
+        }
+      })()
+    )
   );
 };
 
EOF
@@ -33,13 +33,35 @@
export const isChatGPTApp = (): boolean => {
if (typeof window === 'undefined') return false;

const allowedHosts = [
'chat.openai.com',
'chatgpt.com',
'www.chatgpt.com'
];

// Helper to check if a given location belongs to an allowed host
const isAllowedHost = (loc: Location): boolean => {
try {
const { hostname } = new URL(loc.href);
return allowedHosts.includes(hostname);
} catch {
return false;
}
};

return (
window.location.href.includes('chat.openai.com') ||
window.location.href.includes('chatgpt.com') ||
isAllowedHost(window.location) ||
window.__OPENAI_APPS__ !== undefined ||
(window.parent !== window &&
(window.parent.location.href.includes('chat.openai.com') ||
window.parent.location.href.includes('chatgpt.com')))
// Cross-origin access to window.parent.location may throw, so wrap in try/catch.
(() => {
try {
return isAllowedHost(window.parent.location);
} catch {
return false;
}
})()
)
);
};

Copilot is powered by AI and may make mistakes. Always verify output.

return (
window.location.href.includes('chat.openai.com') ||
window.location.href.includes('chatgpt.com') ||

Check failure

Code scanning / CodeQL

Incomplete URL substring sanitization High

'
chatgpt.com
' can be anywhere in the URL, and arbitrary hosts may come before or after it.

Copilot Autofix

AI 5 months ago

To fix the problem, we must parse the URL using the appropriate API and check the host or hostname property rather than performing a substring check on the full URL. We should replace all instances of window.location.href.includes('chatgpt.com') (and similarly for chat.openai.com) with a check that parses the host part of the URL and matches it against an allowlist of acceptable hostnames, e.g., 'chat.openai.com' and 'chatgpt.com' (and their canonical subdomains, if needed).

We'll use the built-in URL class (available in modern browsers and likely for this code) to parse window.location.href and window.parent.location.href.
Change applicable at lines 36-43 in chatgpt-app-sample/lib/chatgpt-apps.ts:

  • Create an allowlist: ['chat.openai.com', 'chatgpt.com']
  • Get window.location.hostname and window.parent.location.hostname
  • Replace the includes() checks with an array .includes() match against the parsed hostname.

No additional package imports are needed. The built-in URL object suffices.


Suggested changeset 1
chatgpt-app-sample/lib/chatgpt-apps.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/chatgpt-app-sample/lib/chatgpt-apps.ts b/chatgpt-app-sample/lib/chatgpt-apps.ts
--- a/chatgpt-app-sample/lib/chatgpt-apps.ts
+++ b/chatgpt-app-sample/lib/chatgpt-apps.ts
@@ -33,13 +33,22 @@
 export const isChatGPTApp = (): boolean => {
   if (typeof window === 'undefined') return false;
 
-  return (
-    window.location.href.includes('chat.openai.com') ||
-    window.location.href.includes('chatgpt.com') ||
-    window.__OPENAI_APPS__ !== undefined ||
-    (window.parent !== window &&
-      (window.parent.location.href.includes('chat.openai.com') ||
-        window.parent.location.href.includes('chatgpt.com')))
-  );
+  const allowedHosts = ['chat.openai.com', 'chatgpt.com'];
+
+  let hostMatch =
+    allowedHosts.includes(window.location.hostname) ||
+    window.__OPENAI_APPS__ !== undefined;
+
+  // Check parent window if it's not the same as current window (cross-origin may throw error)
+  if (!hostMatch && window.parent !== window) {
+    try {
+      hostMatch =
+        allowedHosts.includes(window.parent.location.hostname);
+    } catch (e) {
+      // Ignore cross-origin access errors
+    }
+  }
+
+  return hostMatch;
 };
 
EOF
@@ -33,13 +33,22 @@
export const isChatGPTApp = (): boolean => {
if (typeof window === 'undefined') return false;

return (
window.location.href.includes('chat.openai.com') ||
window.location.href.includes('chatgpt.com') ||
window.__OPENAI_APPS__ !== undefined ||
(window.parent !== window &&
(window.parent.location.href.includes('chat.openai.com') ||
window.parent.location.href.includes('chatgpt.com')))
);
const allowedHosts = ['chat.openai.com', 'chatgpt.com'];

let hostMatch =
allowedHosts.includes(window.location.hostname) ||
window.__OPENAI_APPS__ !== undefined;

// Check parent window if it's not the same as current window (cross-origin may throw error)
if (!hostMatch && window.parent !== window) {
try {
hostMatch =
allowedHosts.includes(window.parent.location.hostname);
} catch (e) {
// Ignore cross-origin access errors
}
}

return hostMatch;
};

Copilot is powered by AI and may make mistakes. Always verify output.
window.location.href.includes('chatgpt.com') ||
window.__OPENAI_APPS__ !== undefined ||
(window.parent !== window &&
(window.parent.location.href.includes('chat.openai.com') ||

Check failure

Code scanning / CodeQL

Incomplete URL substring sanitization High

'
chat.openai.com
' can be anywhere in the URL, and arbitrary hosts may come before or after it.

Copilot Autofix

AI 5 months ago

The best way to fix this problem is to parse the URL using the browser's URL constructor, then check the host (or hostname) field against an explicit whitelist of allowed hosts. This avoids substring checks and ensures only exact matches to desired hosts or subdomains. For each place that currently uses .includes('chat.openai.com') or .includes('chatgpt.com') on window.location.href (or parent), replace it with a parsed check against the host, e.g.:

const allowedHosts = ['chat.openai.com', 'chatgpt.com'];
allowedHosts.includes(new URL(window.location.href).host)

or, if you want to include subdomains, resolve how broad you want the match to be (possibly using .endsWith(...)). Update all four relevant checks (self/parent and both host names).

If you use the URL constructor, no imports are required. If the URL is unparseable, you may want to handle exceptions gracefully.

The changes should stay within the context of function isChatGPTApp, replacing lines where .includes('chat.openai.com') or .includes('chatgpt.com') are called on URLs.


Suggested changeset 1
chatgpt-app-sample/lib/chatgpt-apps.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/chatgpt-app-sample/lib/chatgpt-apps.ts b/chatgpt-app-sample/lib/chatgpt-apps.ts
--- a/chatgpt-app-sample/lib/chatgpt-apps.ts
+++ b/chatgpt-app-sample/lib/chatgpt-apps.ts
@@ -33,13 +33,23 @@
 export const isChatGPTApp = (): boolean => {
   if (typeof window === 'undefined') return false;
 
-  return (
-    window.location.href.includes('chat.openai.com') ||
-    window.location.href.includes('chatgpt.com') ||
-    window.__OPENAI_APPS__ !== undefined ||
-    (window.parent !== window &&
-      (window.parent.location.href.includes('chat.openai.com') ||
-        window.parent.location.href.includes('chatgpt.com')))
-  );
+  try {
+    const allowedHosts = ['chat.openai.com', 'chatgpt.com'];
+    const currentHost = new URL(window.location.href).host;
+    if (allowedHosts.includes(currentHost)) return true;
+    if (window.__OPENAI_APPS__ !== undefined) return true;
+    if (window.parent !== window) {
+      let parentHost: string;
+      try {
+        parentHost = new URL(window.parent.location.href).host;
+      } catch (e) {
+        parentHost = '';
+      }
+      if (allowedHosts.includes(parentHost)) return true;
+    }
+  } catch (e) {
+    // Swallow parsing exceptions
+  }
+  return false;
 };
 
EOF
@@ -33,13 +33,23 @@
export const isChatGPTApp = (): boolean => {
if (typeof window === 'undefined') return false;

return (
window.location.href.includes('chat.openai.com') ||
window.location.href.includes('chatgpt.com') ||
window.__OPENAI_APPS__ !== undefined ||
(window.parent !== window &&
(window.parent.location.href.includes('chat.openai.com') ||
window.parent.location.href.includes('chatgpt.com')))
);
try {
const allowedHosts = ['chat.openai.com', 'chatgpt.com'];
const currentHost = new URL(window.location.href).host;
if (allowedHosts.includes(currentHost)) return true;
if (window.__OPENAI_APPS__ !== undefined) return true;
if (window.parent !== window) {
let parentHost: string;
try {
parentHost = new URL(window.parent.location.href).host;
} catch (e) {
parentHost = '';
}
if (allowedHosts.includes(parentHost)) return true;
}
} catch (e) {
// Swallow parsing exceptions
}
return false;
};

Copilot is powered by AI and may make mistakes. Always verify output.
window.__OPENAI_APPS__ !== undefined ||
(window.parent !== window &&
(window.parent.location.href.includes('chat.openai.com') ||
window.parent.location.href.includes('chatgpt.com')))

Check failure

Code scanning / CodeQL

Incomplete URL substring sanitization High

'
chatgpt.com
' can be anywhere in the URL, and arbitrary hosts may come before or after it.

Copilot Autofix

AI 5 months ago

To address the URL substring check, the function should reliably determine the hostname portion of the location object (rather than scanning for a substring in the whole URL), and compare it to a fixed allow-list of trusted hosts—for example, only chat.openai.com and chatgpt.com (and, optionally, their subdomains if required).

  • Extract the hostname using the location.hostname property.
  • Directly compare hostname against a whitelist (e.g., 'chat.openai.com', 'chatgpt.com').
  • Do this for both window.location and, if checked, window.parent.location.
  • No additional dependencies are required (standard DOM location is sufficient).
  • Only make the change within the affected (shown) code, so edit lines 37-42 so that the domain checks use location.hostname.

Suggested changeset 1
chatgpt-app-sample/lib/chatgpt-apps.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/chatgpt-app-sample/lib/chatgpt-apps.ts b/chatgpt-app-sample/lib/chatgpt-apps.ts
--- a/chatgpt-app-sample/lib/chatgpt-apps.ts
+++ b/chatgpt-app-sample/lib/chatgpt-apps.ts
@@ -33,13 +33,12 @@
 export const isChatGPTApp = (): boolean => {
   if (typeof window === 'undefined') return false;
 
+  const allowedHosts = ['chat.openai.com', 'chatgpt.com'];
   return (
-    window.location.href.includes('chat.openai.com') ||
-    window.location.href.includes('chatgpt.com') ||
+    allowedHosts.includes(window.location.hostname) ||
     window.__OPENAI_APPS__ !== undefined ||
     (window.parent !== window &&
-      (window.parent.location.href.includes('chat.openai.com') ||
-        window.parent.location.href.includes('chatgpt.com')))
+      (allowedHosts.includes(window.parent.location.hostname)))
   );
 };
 
EOF
@@ -33,13 +33,12 @@
export const isChatGPTApp = (): boolean => {
if (typeof window === 'undefined') return false;

const allowedHosts = ['chat.openai.com', 'chatgpt.com'];
return (
window.location.href.includes('chat.openai.com') ||
window.location.href.includes('chatgpt.com') ||
allowedHosts.includes(window.location.hostname) ||
window.__OPENAI_APPS__ !== undefined ||
(window.parent !== window &&
(window.parent.location.href.includes('chat.openai.com') ||
window.parent.location.href.includes('chatgpt.com')))
(allowedHosts.includes(window.parent.location.hostname)))
);
};

Copilot is powered by AI and may make mistakes. Always verify output.
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