diff --git a/docs/CAPTCHA_FIX.md b/docs/CAPTCHA_FIX.md
new file mode 100644
index 0000000..d02730b
--- /dev/null
+++ b/docs/CAPTCHA_FIX.md
@@ -0,0 +1,149 @@
+# CAPTCHA Compatibility Fix
+
+## Problem Statement
+
+When attempting to load CAPTCHA verification systems (reCAPTCHA, hCaptcha, Cloudflare Turnstile, Yandex Cloud, etc.) through the Ultraviolet proxy, the following errors occurred:
+
+```
+Uncaught DataCloneError: Failed to execute 'postMessage' on 'Window':
+A MessagePort could not be cloned because it was not transferred.
+```
+
+Additionally, preload resource warnings appeared:
+```
+A preload for '...' is found, but is not used because the request credentials mode does not match.
+Consider taking a look at crossorigin attribute.
+```
+
+## Root Cause
+
+The issue stems from how Ultraviolet intercepts `postMessage` calls. CAPTCHA systems extensively use `postMessage` with `MessagePort` objects for secure cross-origin communication between iframes. According to the [Structured Clone Algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm), `MessagePort` objects cannot be cloned - they must be explicitly transferred via the `transfer` parameter.
+
+When UV intercepts `postMessage`, it doesn't properly handle the transfer of `MessagePort` objects, causing the `DataCloneError`.
+
+## Solution
+
+### 1. CAPTCHA Patch Script (`/public/captcha-patch.js`)
+
+A standalone script that must be loaded **before** the UV handler. It patches `Window.prototype.postMessage` to:
+
+- Recursively scan the message object for `MessagePort` instances
+- Automatically add found ports to the `transfer` array
+- Properly invoke the native `postMessage` with transfers
+
+**Key Features:**
+- Avoids circular references with `WeakSet` tracking
+- Fallback to original implementation on error
+- Marks itself to prevent UV from overriding the fix
+
+### 2. Enhanced CAPTCHA Handler (`src/utils/captcha-handler.ts`)
+
+Extended to provide comprehensive CAPTCHA support:
+
+**`patchPostMessage()` Function:**
+- Secondary patch layer for iframe contentWindow
+- Handles edge cases UV might miss
+- Ensures proper MessagePort transfer in all contexts
+
+**Preload Resource Monitor:**
+- Watches for `` elements for CAPTCHA resources
+- Automatically adds `crossorigin="anonymous"` attribute
+- Sets appropriate `as` attribute (script, style, font) based on file type
+
+**Supported CAPTCHA Providers:**
+- Google reCAPTCHA v2/v3
+- hCaptcha
+- Cloudflare Turnstile
+- Yandex Cloud CAPTCHA
+- Other providers using similar patterns
+
+### 3. Service Worker Updates (`public/sw.js`)
+
+Added CAPTCHA domain detection for proper request handling:
+- Preserves credentials for CAPTCHA cookies
+- Ensures proper headers for CAPTCHA requests
+- Handles special routing for verification domains
+
+### 4. Proxy Initialization (`src/utils/proxy.ts`)
+
+Modified to load the CAPTCHA patch **before** UV scripts:
+```javascript
+createScript("/captcha-patch.js", false); // Load first
+createScript("/vu/uv.bundle.js", true);
+createScript("/vu/uv.config.js", true);
+```
+
+## Technical Details
+
+### MessagePort Transfer
+
+The fix implements the proper way to handle MessagePorts in `postMessage`:
+
+```javascript
+// ❌ WRONG - Causes DataCloneError
+window.postMessage(messageWithPort, "*");
+
+// ✅ CORRECT - Transfers the port
+window.postMessage(messageWithPort, "*", [messagePort]);
+```
+
+Our patch automatically detects ports in the message and constructs the proper transfer array.
+
+### Crossorigin Attribute
+
+CAPTCHA resources often load from different origins (e.g., `gstatic.com` for reCAPTCHA). Preload hints must match the credential mode:
+
+```html
+
+
+
+
+
+```
+
+## Testing
+
+To test CAPTCHA functionality:
+
+1. Build the project: `npm run build`
+2. Start the server: `npm start`
+3. Navigate through the proxy to a site with CAPTCHA:
+ - reCAPTCHA: https://www.google.com/recaptcha/api2/demo
+ - hCaptcha: https://www.hcaptcha.com/
+ - Cloudflare Turnstile: Sites with Cloudflare bot protection
+
+The CAPTCHA should load and function properly without console errors.
+
+## Browser Compatibility
+
+The fix is compatible with all modern browsers that support:
+- `Window.prototype.postMessage`
+- `MessagePort` API
+- `MutationObserver`
+- `WeakSet` (for circular reference detection)
+
+This includes:
+- Chrome/Edge 60+
+- Firefox 55+
+- Safari 11+
+
+## Security Considerations
+
+- The patch does not modify the security model of `postMessage`
+- MessagePorts are still transferred (not cloned), maintaining their single-owner semantics
+- CAPTCHA verification still occurs server-side; this only fixes client-side communication
+- No sensitive data is exposed or logged
+
+## Future Improvements
+
+Potential enhancements:
+- Upstream fix to Ultraviolet to natively handle MessagePort transfers
+- Performance optimization for large object trees
+- Support for additional transferable objects (e.g., ArrayBuffer)
+
+## References
+
+- [MDN: Window.postMessage()](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage)
+- [MDN: MessagePort](https://developer.mozilla.org/en-US/docs/Web/API/MessagePort)
+- [Structured Clone Algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm)
+- [HTML Spec: MessagePort transfer](https://html.spec.whatwg.org/multipage/web-messaging.html#message-ports)
diff --git a/public/captcha-patch.js b/public/captcha-patch.js
new file mode 100644
index 0000000..8cf1f5a
--- /dev/null
+++ b/public/captcha-patch.js
@@ -0,0 +1,95 @@
+/**
+ * CAPTCHA Compatibility Patch
+ * This script must be loaded BEFORE UV handler to fix MessagePort handling in postMessage
+ *
+ * Fixes the error: "DataCloneError: Failed to execute 'postMessage' on 'Window':
+ * A MessagePort could not be cloned because it was not transferred."
+ */
+
+(function () {
+ "use strict";
+
+ // Only run once
+ if (window.__captchaPatchApplied) return;
+ window.__captchaPatchApplied = true;
+
+ // Store the original postMessage method
+ const originalWindowPostMessage = Window.prototype.postMessage;
+
+ /**
+ * Enhanced postMessage that properly handles MessagePort transfers
+ * This is critical for CAPTCHA systems (reCAPTCHA, hCaptcha, Cloudflare Turnstile, Yandex)
+ */
+ Window.prototype.postMessage = function (message, targetOrigin, transfer) {
+ try {
+ // If transfer is already provided, use it directly
+ if (transfer !== undefined) {
+ return originalWindowPostMessage.call(this, message, targetOrigin, transfer);
+ }
+
+ // Extract MessagePorts from the message to transfer them properly
+ const ports = [];
+
+ if (message && typeof message === "object") {
+ // Recursively find MessagePorts in the message
+ const findPorts = (obj, visited) => {
+ if (!obj || typeof obj !== "object") return;
+
+ // Avoid circular references
+ visited = visited || new WeakSet();
+ if (visited.has(obj)) return;
+ visited.add(obj);
+
+ // Check if this is a MessagePort
+ if (obj instanceof MessagePort) {
+ ports.push(obj);
+ return;
+ }
+
+ // Check arrays
+ if (Array.isArray(obj)) {
+ for (let i = 0; i < obj.length; i++) {
+ findPorts(obj[i], visited);
+ }
+ return;
+ }
+
+ // Check object properties
+ for (const key in obj) {
+ try {
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
+ findPorts(obj[key], visited);
+ }
+ } catch (e) {
+ // Ignore errors accessing properties (e.g., cross-origin)
+ }
+ }
+ };
+
+ findPorts(message);
+ }
+
+ // If we found MessagePorts, transfer them
+ if (ports.length > 0) {
+ return originalWindowPostMessage.call(this, message, targetOrigin, ports);
+ }
+
+ // Otherwise, use the original call
+ return originalWindowPostMessage.call(this, message, targetOrigin);
+ } catch (error) {
+ // If our enhanced version fails, try the original
+ console.warn("[CAPTCHA Patch] Enhanced postMessage failed, using fallback:", error);
+ try {
+ return originalWindowPostMessage.call(this, message, targetOrigin, transfer);
+ } catch (fallbackError) {
+ console.error("[CAPTCHA Patch] Original postMessage also failed:", fallbackError);
+ throw fallbackError;
+ }
+ }
+ };
+
+ // Mark the patched method to prevent UV from breaking it
+ Window.prototype.postMessage.__captchaPatched = true;
+
+ console.log("[CAPTCHA Patch] MessagePort handling enabled for CAPTCHA compatibility");
+})();
diff --git a/public/sw.js b/public/sw.js
index efad95a..d9d86fb 100644
--- a/public/sw.js
+++ b/public/sw.js
@@ -18,7 +18,11 @@ const CAPTCHA_DOMAINS = [
"newassets.hcaptcha.com",
"challenges.cloudflare.com",
"cloudflare.com/cdn-cgi/challenge",
- "turnstile.cloudflare.com"
+ "turnstile.cloudflare.com",
+ "yandex.com/captcha",
+ "yandex.ru/captcha",
+ "yandex.net/captcha",
+ "captcha-delivery.com"
];
// Helper function to check if URL is CAPTCHA-related
diff --git a/src/utils/captcha-handler.ts b/src/utils/captcha-handler.ts
index 47a74e5..3b6e7ac 100644
--- a/src/utils/captcha-handler.ts
+++ b/src/utils/captcha-handler.ts
@@ -13,7 +13,11 @@ const CAPTCHA_DOMAINS = [
"gstatic.com",
"hcaptcha.com",
"cloudflare.com",
- "challenges.cloudflare.com"
+ "challenges.cloudflare.com",
+ "yandex.com",
+ "yandex.ru",
+ "yandex.net",
+ "captcha-delivery.com"
];
/**
@@ -23,6 +27,9 @@ const CAPTCHA_DOMAINS = [
export function initializeCaptchaHandlers() {
if (typeof window === "undefined") return;
+ // Fix postMessage to properly handle MessagePorts (critical for CAPTCHA functionality)
+ patchPostMessage();
+
// Ensure global CAPTCHA callbacks are accessible
if (!window.___grecaptcha_cfg) {
window.___grecaptcha_cfg = { clients: {} };
@@ -32,6 +39,7 @@ export function initializeCaptchaHandlers() {
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
+ // Handle iframe elements
if (node instanceof HTMLIFrameElement) {
const src = node.src || "";
// Check if this is a CAPTCHA iframe
@@ -39,7 +47,9 @@ export function initializeCaptchaHandlers() {
src.includes("recaptcha") ||
src.includes("hcaptcha") ||
src.includes("challenges.cloudflare.com") ||
- src.includes("turnstile")
+ src.includes("turnstile") ||
+ src.includes("yandex") ||
+ src.includes("captcha-delivery")
) {
// Ensure the iframe has proper sandbox permissions
if (node.sandbox && node.sandbox.length > 0) {
@@ -54,6 +64,41 @@ export function initializeCaptchaHandlers() {
}
}
}
+
+ // Handle link preload elements for CAPTCHA resources
+ if (node instanceof HTMLLinkElement && node.rel === "preload") {
+ const href = node.href || "";
+ // Check if this is a CAPTCHA-related preload
+ if (
+ href.includes("recaptcha") ||
+ href.includes("hcaptcha") ||
+ href.includes("gstatic.com") ||
+ href.includes("cloudflare.com") ||
+ href.includes("yandex") ||
+ href.includes("captcha-delivery")
+ ) {
+ // Add crossorigin attribute to avoid credential mode mismatch
+ if (!node.hasAttribute("crossorigin")) {
+ node.setAttribute("crossorigin", "anonymous");
+ }
+
+ // Ensure proper 'as' attribute
+ if (!node.hasAttribute("as")) {
+ // Determine 'as' value based on URL
+ if (href.endsWith(".js") || href.includes(".js?")) {
+ node.setAttribute("as", "script");
+ } else if (href.endsWith(".css") || href.includes(".css?")) {
+ node.setAttribute("as", "style");
+ } else if (
+ href.endsWith(".woff2") ||
+ href.endsWith(".woff") ||
+ href.endsWith(".ttf")
+ ) {
+ node.setAttribute("as", "font");
+ }
+ }
+ }
+ }
});
});
});
@@ -162,6 +207,133 @@ function enhanceNetworkRequests() {
Object.setPrototypeOf(window.XMLHttpRequest.prototype, OriginalXHR.prototype);
}
+/**
+ * Patch postMessage to properly handle MessagePort transfers
+ * This is critical for CAPTCHA systems that use MessagePorts for communication
+ */
+function patchPostMessage() {
+ // Store the original postMessage method
+ const originalPostMessage = window.postMessage;
+ const originalWindowPostMessage = Window.prototype.postMessage;
+
+ /**
+ * Enhanced postMessage that properly handles MessagePort transfers
+ */
+ const enhancedPostMessage = function (
+ this: Window,
+ message: any,
+ targetOrigin: string,
+ transfer?: any[]
+ ) {
+ try {
+ // Extract MessagePorts from the message
+ const ports: MessagePort[] = [];
+
+ // Check if transfer array is provided
+ if (transfer && Array.isArray(transfer)) {
+ // Transfer array already contains ports, use it directly
+ return originalWindowPostMessage.call(this, message, targetOrigin, transfer);
+ }
+
+ // Check if the message contains MessagePort objects
+ if (message && typeof message === "object") {
+ // Recursively find MessagePorts in the message
+ const findPorts = (obj: any, visited = new WeakSet()): void => {
+ if (!obj || typeof obj !== "object") return;
+ if (visited.has(obj)) return;
+ visited.add(obj);
+
+ if (obj instanceof MessagePort) {
+ ports.push(obj);
+ return;
+ }
+
+ // Check arrays
+ if (Array.isArray(obj)) {
+ for (const item of obj) {
+ findPorts(item, visited);
+ }
+ return;
+ }
+
+ // Check object properties
+ for (const key in obj) {
+ try {
+ if (obj.hasOwnProperty(key)) {
+ findPorts(obj[key], visited);
+ }
+ } catch (e) {
+ // Ignore errors accessing properties
+ }
+ }
+ };
+
+ findPorts(message);
+ }
+
+ // If we found MessagePorts, transfer them
+ if (ports.length > 0) {
+ return originalWindowPostMessage.call(this, message, targetOrigin, ports);
+ }
+
+ // Otherwise, use the original call
+ return originalWindowPostMessage.call(this, message, targetOrigin, transfer);
+ } catch (error) {
+ // If our enhanced version fails, fall back to original
+ console.warn("Enhanced postMessage failed, using original:", error);
+ return originalWindowPostMessage.call(this, message, targetOrigin, transfer);
+ }
+ };
+
+ // Override Window.prototype.postMessage
+ try {
+ Object.defineProperty(Window.prototype, "postMessage", {
+ value: enhancedPostMessage,
+ writable: true,
+ enumerable: true,
+ configurable: true
+ });
+ } catch (e) {
+ console.warn("Failed to override Window.prototype.postMessage:", e);
+ }
+
+ // Also patch the window.postMessage directly
+ try {
+ Object.defineProperty(window, "postMessage", {
+ value: enhancedPostMessage.bind(window),
+ writable: true,
+ enumerable: true,
+ configurable: true
+ });
+ } catch (e) {
+ console.warn("Failed to override window.postMessage:", e);
+ }
+
+ // Patch HTMLIFrameElement.contentWindow.postMessage
+ const originalIFrameContentWindowGetter = Object.getOwnPropertyDescriptor(
+ HTMLIFrameElement.prototype,
+ "contentWindow"
+ );
+
+ if (originalIFrameContentWindowGetter) {
+ Object.defineProperty(HTMLIFrameElement.prototype, "contentWindow", {
+ get: function () {
+ const contentWindow = originalIFrameContentWindowGetter.get!.call(this);
+ if (contentWindow && contentWindow.postMessage) {
+ try {
+ contentWindow.postMessage = enhancedPostMessage.bind(contentWindow);
+ } catch (e) {
+ // Ignore cross-origin access errors
+ }
+ }
+ return contentWindow;
+ },
+ enumerable: true,
+ configurable: true
+ });
+ }
+}
+
/**
* Global declaration for reCAPTCHA config
*/
diff --git a/src/utils/proxy.ts b/src/utils/proxy.ts
index b65988e..80d80c6 100644
--- a/src/utils/proxy.ts
+++ b/src/utils/proxy.ts
@@ -151,6 +151,7 @@ class SW {
});
});
};
+ createScript("/captcha-patch.js", false); // Load CAPTCHA patch first
createScript("/vu/uv.bundle.js", true);
createScript("/vu/uv.config.js", true);
createScript("/marcs/scramjet.all.js", true);