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. } /**