From 774cbf061194ae50bd31e028c1c8ba239a8ace3a Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 26 Nov 2025 18:07:09 +0000
Subject: [PATCH 1/5] Initial plan
From 2ef3e23857af3b4a686e1e533e56f4f76a45fdfe Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 26 Nov 2025 18:14:12 +0000
Subject: [PATCH 2/5] Fix CAPTCHA loading: add missing domains, improve iframe
handling, and enhance postMessage support
Co-authored-by: sriail <225764385+sriail@users.noreply.github.com>
---
public/sw.js | 124 ++++++++++++++++++++++++----
src/utils/captcha-handler.ts | 151 +++++++++++++++++++++++++++++------
2 files changed, 236 insertions(+), 39 deletions(-)
diff --git a/public/sw.js b/public/sw.js
index 3266d21..90155b8 100644
--- a/public/sw.js
+++ b/public/sw.js
@@ -20,16 +20,27 @@ const sj = new ScramjetServiceWorker({
// Enhanced CAPTCHA and Cloudflare verification support
// List of CAPTCHA and verification domains that need special handling
const CAPTCHA_DOMAINS = [
+ // reCAPTCHA domains
"google.com/recaptcha",
"www.google.com/recaptcha",
"recaptcha.net",
"www.recaptcha.net",
"gstatic.com/recaptcha",
+ "www.gstatic.com/recaptcha",
+ // hCaptcha domains
"hcaptcha.com",
+ "www.hcaptcha.com",
"newassets.hcaptcha.com",
+ "assets.hcaptcha.com",
+ "imgs.hcaptcha.com",
+ "js.hcaptcha.com",
+ // Cloudflare Turnstile domains
"challenges.cloudflare.com",
"cloudflare.com/cdn-cgi/challenge",
- "turnstile.cloudflare.com"
+ "turnstile.cloudflare.com",
+ // Additional verification APIs
+ "api.hcaptcha.com",
+ "api2.hcaptcha.com"
];
// Domains that use heavy cookies and complex browser services
@@ -144,20 +155,48 @@ self.addEventListener("fetch", function (event) {
);
});
-// Script to inject into proxied pages to intercept new tab/window attempts
+// Script to inject into proxied pages for CAPTCHA support and new tab interception
const INTERCEPTOR_SCRIPT = `
`;
diff --git a/src/utils/captcha-handler.ts b/src/utils/captcha-handler.ts
index 9ddf6ac..f3b8909 100644
--- a/src/utils/captcha-handler.ts
+++ b/src/utils/captcha-handler.ts
@@ -9,12 +9,26 @@
* List of CAPTCHA and verification-related domains
*/
const CAPTCHA_DOMAINS = [
+ // reCAPTCHA domains
"google.com",
+ "www.google.com",
"recaptcha.net",
+ "www.recaptcha.net",
"gstatic.com",
+ "www.gstatic.com",
+ // hCaptcha domains
"hcaptcha.com",
+ "www.hcaptcha.com",
+ "newassets.hcaptcha.com",
+ "assets.hcaptcha.com",
+ "imgs.hcaptcha.com",
+ "js.hcaptcha.com",
+ "api.hcaptcha.com",
+ "api2.hcaptcha.com",
+ // Cloudflare Turnstile domains
"cloudflare.com",
- "challenges.cloudflare.com"
+ "challenges.cloudflare.com",
+ "turnstile.cloudflare.com"
];
/**
@@ -49,31 +63,65 @@ export function initializeCaptchaHandlers() {
window.___grecaptcha_cfg = { clients: {} };
}
+ // Initialize hCaptcha global object
+ if (!window.hcaptcha) {
+ window.hcaptcha = {};
+ }
+
+ // Initialize Turnstile global object
+ if (!window.turnstile) {
+ window.turnstile = {};
+ }
+
+ // Check if URL is CAPTCHA-related
+ const isCaptchaUrl = (url: string): boolean => {
+ const urlLower = url.toLowerCase();
+ return CAPTCHA_DOMAINS.some((domain) => urlLower.includes(domain));
+ };
+
+ // Setup CAPTCHA iframe with proper permissions
+ const setupCaptchaIframe = (iframe: HTMLIFrameElement) => {
+ const src = iframe.src || iframe.getAttribute("src") || "";
+ if (
+ src.includes("recaptcha") ||
+ src.includes("hcaptcha") ||
+ src.includes("challenges.cloudflare.com") ||
+ src.includes("turnstile") ||
+ isCaptchaUrl(src)
+ ) {
+ // Ensure the iframe has proper sandbox permissions
+ if (iframe.sandbox && iframe.sandbox.length > 0) {
+ iframe.sandbox.add("allow-same-origin");
+ iframe.sandbox.add("allow-scripts");
+ iframe.sandbox.add("allow-forms");
+ iframe.sandbox.add("allow-popups");
+ iframe.sandbox.add("allow-popups-to-escape-sandbox");
+ }
+
+ // Ensure credentials are included for CAPTCHA cookies
+ if (iframe.getAttribute("credentialless") !== null) {
+ iframe.removeAttribute("credentialless");
+ }
+
+ // Add proper allow attribute for permissions policy
+ const allow = iframe.getAttribute("allow") || "";
+ if (!allow.includes("cross-origin-isolated")) {
+ iframe.setAttribute("allow", allow + (allow ? "; " : "") + "cross-origin-isolated");
+ }
+ }
+ };
+
// Monitor for CAPTCHA iframe creation and ensure proper setup
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
if (node instanceof HTMLIFrameElement) {
- const src = node.src || "";
- // Check if this is a CAPTCHA iframe
- if (
- src.includes("recaptcha") ||
- src.includes("hcaptcha") ||
- src.includes("challenges.cloudflare.com") ||
- src.includes("turnstile")
- ) {
- // Ensure the iframe has proper sandbox permissions
- if (node.sandbox && node.sandbox.length > 0) {
- node.sandbox.add("allow-same-origin");
- node.sandbox.add("allow-scripts");
- node.sandbox.add("allow-forms");
- }
-
- // Ensure credentials are included for CAPTCHA cookies
- if (node.getAttribute("credentialless") !== null) {
- node.removeAttribute("credentialless");
- }
- }
+ setupCaptchaIframe(node);
+ } else if (node instanceof HTMLElement) {
+ // Also check for iframes within added nodes
+ node.querySelectorAll("iframe").forEach((iframe) => {
+ setupCaptchaIframe(iframe as HTMLIFrameElement);
+ });
}
});
});
@@ -85,6 +133,11 @@ export function initializeCaptchaHandlers() {
subtree: true
});
+ // Setup existing iframes
+ document.querySelectorAll("iframe").forEach((iframe) => {
+ setupCaptchaIframe(iframe as HTMLIFrameElement);
+ });
+
// Ensure cookies are properly handled for CAPTCHA tokens and heavy cookie sites
enhanceCookieHandling();
@@ -93,6 +146,9 @@ export function initializeCaptchaHandlers() {
// Add storage persistence for better cookie support
enhanceStoragePersistence();
+
+ // Setup postMessage handler for CAPTCHA communication
+ setupPostMessageHandler();
}
/**
@@ -219,13 +275,60 @@ function enhanceStoragePersistence() {
}
/**
- * Global declaration for reCAPTCHA config
+ * Setup postMessage handler to enable CAPTCHA communication
+ * This ensures that CAPTCHA widgets can communicate with their parent pages
+ */
+function setupPostMessageHandler() {
+ // Listen for CAPTCHA-related messages
+ window.addEventListener("message", (event) => {
+ // Check if this is a CAPTCHA-related message
+ const origin = event.origin || "";
+ const isCaptchaOrigin = CAPTCHA_DOMAINS.some((domain) =>
+ origin.toLowerCase().includes(domain)
+ );
+
+ if (isCaptchaOrigin) {
+ // Allow CAPTCHA messages to be processed normally
+ // The browser's default handling will take care of it
+ return;
+ }
+ });
+
+ // Enhance postMessage to allow CAPTCHA communication
+ const originalPostMessage = window.postMessage.bind(window);
+ window.postMessage = function (
+ message: unknown,
+ targetOrigin: string,
+ transfer?: Transferable[]
+ ) {
+ // Check if target is a CAPTCHA origin
+ if (targetOrigin && targetOrigin !== "*") {
+ const isCaptchaTarget = CAPTCHA_DOMAINS.some((domain) =>
+ targetOrigin.toLowerCase().includes(domain)
+ );
+ // For CAPTCHA targets, use wildcard to avoid cross-origin issues in proxy
+ if (isCaptchaTarget) {
+ targetOrigin = "*";
+ }
+ }
+ return originalPostMessage(message, targetOrigin, transfer);
+ };
+}
+
+/**
+ * Global declaration for CAPTCHA configs
*/
declare global {
interface Window {
___grecaptcha_cfg?: {
- clients: Record;
- [key: string]: any;
+ clients: Record;
+ [key: string]: unknown;
+ };
+ hcaptcha?: {
+ [key: string]: unknown;
+ };
+ turnstile?: {
+ [key: string]: unknown;
};
}
}
From e2112c907516377a059639cefa668a878729b38d Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 26 Nov 2025 18:20:01 +0000
Subject: [PATCH 3/5] Add security documentation for postMessage wildcard usage
in CAPTCHA handling
Co-authored-by: sriail <225764385+sriail@users.noreply.github.com>
---
public/sw.js | 7 +++++--
src/utils/captcha-handler.ts | 4 ++++
2 files changed, 9 insertions(+), 2 deletions(-)
diff --git a/public/sw.js b/public/sw.js
index 90155b8..4719611 100644
--- a/public/sw.js
+++ b/public/sw.js
@@ -179,10 +179,10 @@ const INTERCEPTOR_SCRIPT = `
window.___grecaptcha_cfg = { clients: {} };
}
if (typeof window.hcaptcha === 'undefined') {
- window.hcaptcha = window.hcaptcha || {};
+ window.hcaptcha = {};
}
if (typeof window.turnstile === 'undefined') {
- window.turnstile = window.turnstile || {};
+ window.turnstile = {};
}
// Intercept window.open but allow CAPTCHA-related popups
@@ -206,6 +206,9 @@ const INTERCEPTOR_SCRIPT = `
};
// Enhanced postMessage handler for CAPTCHA communication
+ // NOTE: Using '*' as targetOrigin is required in proxy environment because
+ // CAPTCHA domains are rewritten and cross-origin checks would fail otherwise.
+ // This is a necessary tradeoff for CAPTCHA functionality within the proxy.
var originalPostMessage = window.postMessage;
window.postMessage = function(message, targetOrigin, transfer) {
// Allow all CAPTCHA-related postMessage communications
diff --git a/src/utils/captcha-handler.ts b/src/utils/captcha-handler.ts
index f3b8909..81eef09 100644
--- a/src/utils/captcha-handler.ts
+++ b/src/utils/captcha-handler.ts
@@ -277,6 +277,10 @@ function enhanceStoragePersistence() {
/**
* Setup postMessage handler to enable CAPTCHA communication
* This ensures that CAPTCHA widgets can communicate with their parent pages
+ *
+ * NOTE: Using '*' as targetOrigin is required in proxy environment because
+ * CAPTCHA domains are rewritten and cross-origin checks would fail otherwise.
+ * This is a necessary tradeoff for CAPTCHA functionality within the proxy.
*/
function setupPostMessageHandler() {
// Listen for CAPTCHA-related messages
From 28d71450b69b8ae4af7e5df5af47faba339d5646 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 26 Nov 2025 18:54:28 +0000
Subject: [PATCH 4/5] Fix postMessage DataCloneError by removing override that
breaks MessagePort transfers
Co-authored-by: sriail <225764385+sriail@users.noreply.github.com>
---
public/sw.js | 13 -------------
src/utils/captcha-handler.ts | 31 +++++--------------------------
2 files changed, 5 insertions(+), 39 deletions(-)
diff --git a/public/sw.js b/public/sw.js
index 4719611..a8c6f03 100644
--- a/public/sw.js
+++ b/public/sw.js
@@ -205,19 +205,6 @@ const INTERCEPTOR_SCRIPT = `
return null;
};
- // Enhanced postMessage handler for CAPTCHA communication
- // NOTE: Using '*' as targetOrigin is required in proxy environment because
- // CAPTCHA domains are rewritten and cross-origin checks would fail otherwise.
- // This is a necessary tradeoff for CAPTCHA functionality within the proxy.
- var originalPostMessage = window.postMessage;
- window.postMessage = function(message, targetOrigin, transfer) {
- // Allow all CAPTCHA-related postMessage communications
- if (targetOrigin && isCaptchaUrl(targetOrigin)) {
- targetOrigin = '*';
- }
- return originalPostMessage.call(window, message, targetOrigin, transfer);
- };
-
// Monitor for CAPTCHA iframes and ensure proper setup
function setupCaptchaIframe(iframe) {
var src = iframe.src || iframe.getAttribute('src') || '';
diff --git a/src/utils/captcha-handler.ts b/src/utils/captcha-handler.ts
index 81eef09..1926b11 100644
--- a/src/utils/captcha-handler.ts
+++ b/src/utils/captcha-handler.ts
@@ -275,15 +275,11 @@ function enhanceStoragePersistence() {
}
/**
- * Setup postMessage handler to enable CAPTCHA communication
+ * Setup message handler to listen for CAPTCHA-related messages
* This ensures that CAPTCHA widgets can communicate with their parent pages
- *
- * NOTE: Using '*' as targetOrigin is required in proxy environment because
- * CAPTCHA domains are rewritten and cross-origin checks would fail otherwise.
- * This is a necessary tradeoff for CAPTCHA functionality within the proxy.
*/
function setupPostMessageHandler() {
- // Listen for CAPTCHA-related messages
+ // Listen for CAPTCHA-related messages and allow them to be processed normally
window.addEventListener("message", (event) => {
// Check if this is a CAPTCHA-related message
const origin = event.origin || "";
@@ -297,26 +293,9 @@ function setupPostMessageHandler() {
return;
}
});
-
- // Enhance postMessage to allow CAPTCHA communication
- const originalPostMessage = window.postMessage.bind(window);
- window.postMessage = function (
- message: unknown,
- targetOrigin: string,
- transfer?: Transferable[]
- ) {
- // Check if target is a CAPTCHA origin
- if (targetOrigin && targetOrigin !== "*") {
- const isCaptchaTarget = CAPTCHA_DOMAINS.some((domain) =>
- targetOrigin.toLowerCase().includes(domain)
- );
- // For CAPTCHA targets, use wildcard to avoid cross-origin issues in proxy
- if (isCaptchaTarget) {
- targetOrigin = "*";
- }
- }
- return originalPostMessage(message, targetOrigin, transfer);
- };
+ // Note: We don't override postMessage as it can break transferable objects
+ // like MessagePort. The proxy should handle cross-origin issues at the
+ // service worker level instead.
}
/**
From 9eb257b2b4823fe698d97661758091bd052dcffa Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 26 Nov 2025 18:56:14 +0000
Subject: [PATCH 5/5] Clean up setupPostMessageHandler to remove unused event
listener
Co-authored-by: sriail <225764385+sriail@users.noreply.github.com>
---
src/utils/captcha-handler.ts | 27 ++++++++-------------------
1 file changed, 8 insertions(+), 19 deletions(-)
diff --git a/src/utils/captcha-handler.ts b/src/utils/captcha-handler.ts
index 1926b11..98c4ba7 100644
--- a/src/utils/captcha-handler.ts
+++ b/src/utils/captcha-handler.ts
@@ -275,27 +275,16 @@ function enhanceStoragePersistence() {
}
/**
- * Setup message handler to listen for CAPTCHA-related messages
- * This ensures that CAPTCHA widgets can communicate with their parent pages
+ * Setup message handler placeholder for CAPTCHA-related messages
+ * This function exists to be called as part of initialization but no longer
+ * modifies postMessage behavior since overriding it broke MessagePort transfers
*/
function setupPostMessageHandler() {
- // Listen for CAPTCHA-related messages and allow them to be processed normally
- window.addEventListener("message", (event) => {
- // Check if this is a CAPTCHA-related message
- const origin = event.origin || "";
- const isCaptchaOrigin = CAPTCHA_DOMAINS.some((domain) =>
- origin.toLowerCase().includes(domain)
- );
-
- if (isCaptchaOrigin) {
- // Allow CAPTCHA messages to be processed normally
- // The browser's default handling will take care of it
- return;
- }
- });
- // Note: We don't override postMessage as it can break transferable objects
- // like MessagePort. The proxy should handle cross-origin issues at the
- // service worker level instead.
+ // Note: We intentionally don't override postMessage as it can break
+ // transferable objects like MessagePort. The proxy handles cross-origin
+ // issues at the service worker level instead.
+ // CAPTCHA widgets communicate via postMessage with their own origins,
+ // which works natively without intervention.
}
/**