// WebRTC IP-leak guard. // // Even with --force-webrtc-ip-handling-policy, RTCPeerConnection can surface // ICE candidates that embed a raw local/public IP literal — a bot/deanon tell // that CreepJS and others read. This wrapper strips ONLY candidates that carry // a non-mDNS raw IP literal; mDNS ".local" candidates and all other WebRTC // behavior (offer/answer, data channels) are left intact so real WebRTC keeps // working. // // Technique adapted (reimplemented) from puppeteer-extra-plugin-stealth // (navigator.mediaDevices / webrtc) — see STEALTH-THIRD-PARTY-NOTICES.md. try { const RTCP = typeof RTCPeerConnection !== "undefined" ? RTCPeerConnection : undefined; if (RTCP && RTCP.prototype) { // A raw IPv4/IPv6 literal that is NOT an mDNS ".local" hostname. const rawIpInCandidate = (candidate) => { if (typeof candidate !== "string" || candidate.length === 0) return false; if (/\.local\b/i.test(candidate)) return false; // mDNS obfuscated — safe const ipv4 = /\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/; const ipv6 = /\b(?:[0-9a-f]{1,4}:){2,}[0-9a-f]{0,4}\b/i; return ipv4.test(candidate) || ipv6.test(candidate); }; const origAddEventListener = RTCP.prototype.addEventListener; RTCP.prototype.addEventListener = new Window_Proxy(origAddEventListener, { apply(target, ctx, args) { if (args[0] === "icecandidate" && typeof args[1] === "function") { const userCb = args[1]; const wrapped = function (event) { if (event && event.candidate && rawIpInCandidate(event.candidate.candidate)) { return; // drop the raw-IP candidate; keep gathering others } return Reflect.apply(userCb, this, arguments); }; return Reflect.apply(target, ctx, [args[0], wrapped, args[2]]); } return Reflect.apply(target, ctx, args); }, }); // Also guard the onicecandidate setter path. const desc = Object_getOwnPropertyDescriptor(RTCP.prototype, "onicecandidate"); if (desc && desc.set) { Object_defineProperty(RTCP.prototype, "onicecandidate", { configurable: true, enumerable: desc.enumerable, get: desc.get, set(cb) { if (typeof cb !== "function") return desc.set.call(this, cb); const wrapped = function (event) { if (event && event.candidate && rawIpInCandidate(event.candidate.candidate)) return; return Reflect.apply(cb, this, arguments); }; return desc.set.call(this, wrapped); }, }); } } } catch (e) {}