Spaces:
Paused
Paused
| /** | |
| * stealth-engine.js β Motor de Stealth Completo para Zelin | |
| * ========================================================== | |
| * Basado en investigaciΓ³n real 2025-2026: | |
| * | |
| * HALLAZGO CLAVE: "Blocking or randomizing canvas fingerprinting is easily detected | |
| * because the fingerprint changes on every page load, which is itself suspicious. | |
| * Natural fingerprints that match real device configurations are superior." | |
| * β Multilogin Research, 2025 | |
| * | |
| * HALLAZGO CLAVE 2: "BotBrowser's key advantage: Canvas, WebGL, AudioContext, | |
| * and Fonts must be CONSISTENT across the session. Inconsistencies instantly | |
| * expose fake fingerprints." | |
| * β GeeTest Anti-Bot Research, Nov 2025 | |
| * | |
| * ESTRATEGIA: Fingerprint consistente + realista, NO aleatorio en cada request | |
| * β Generar UNA identidad de dispositivo por sesiΓ³n | |
| * β Mantenerla IDΓNTICA en todas las pΓ‘ginas de esa sesiΓ³n | |
| * β Que corresponda a una configuraciΓ³n de hardware que PODRΓA EXISTIR | |
| */ | |
| import { createHash } from 'crypto'; | |
| // ββ Pool de configuraciones reales de dispositivos βββββββββββββββββββββββββββ | |
| // ExtraΓdas de datasets de fingerprints reales (BrowserLeaks, CreepJS stats 2025) | |
| // CRΓTICO: cada configuraciΓ³n debe ser INTERNAMENTE CONSISTENTE | |
| // (GPU vendor debe coincidir con canvas output, etc.) | |
| const REAL_DEVICE_CONFIGS = [ | |
| { | |
| // Windows 11 + Chrome 120 + Intel UHD 770 (configuraciΓ³n mΓ‘s comΓΊn ~18% de usuarios) | |
| platform : 'Win32', | |
| oscpu : 'Windows NT 10.0', | |
| vendor : 'Google Inc.', | |
| renderer : 'ANGLE (Intel, Intel(R) UHD Graphics 770 Direct3D11 vs_5_0 ps_5_0, D3D11)', | |
| vendorUnmasked : 'Intel', | |
| rendererUnmasked: 'Intel(R) UHD Graphics 770', | |
| hardwareConcurrency: 8, | |
| deviceMemory : 8, | |
| maxTouchPoints : 0, | |
| screenRes : [1920, 1080], | |
| colorDepth : 24, | |
| pixelRatio : 1, | |
| timezone : 'America/Mexico_City', | |
| language : 'es-MX', | |
| fonts : ['Arial', 'Calibri', 'Cambria', 'Comic Sans MS', 'Consolas', 'Courier New', 'Georgia', 'Segoe UI', 'Tahoma', 'Times New Roman', 'Trebuchet MS', 'Verdana'], | |
| plugins : ['Chrome PDF Plugin', 'Chrome PDF Viewer', 'Native Client'], | |
| chromeVersion : '120.0.6099.130', | |
| userAgent : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', | |
| }, | |
| { | |
| // Windows 10 + Chrome 119 + NVIDIA RTX 3060 (gamer setup) | |
| platform : 'Win32', | |
| oscpu : 'Windows NT 10.0', | |
| vendor : 'Google Inc.', | |
| renderer : 'ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 Direct3D11 vs_5_0 ps_5_0, D3D11)', | |
| vendorUnmasked : 'NVIDIA Corporation', | |
| rendererUnmasked: 'NVIDIA GeForce RTX 3060', | |
| hardwareConcurrency: 12, | |
| deviceMemory : 16, | |
| maxTouchPoints : 0, | |
| screenRes : [2560, 1440], | |
| colorDepth : 24, | |
| pixelRatio : 1, | |
| timezone : 'America/Mexico_City', | |
| language : 'es-MX', | |
| fonts : ['Arial', 'Calibri', 'Cambria', 'Consolas', 'Courier New', 'Georgia', 'Impact', 'Segoe UI', 'Tahoma', 'Times New Roman', 'Verdana'], | |
| plugins : ['Chrome PDF Plugin', 'Chrome PDF Viewer'], | |
| chromeVersion : '119.0.6045.200', | |
| userAgent : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36', | |
| }, | |
| { | |
| // macOS 14 + Chrome 120 + Apple M2 | |
| platform : 'MacIntel', | |
| oscpu : 'Intel Mac OS X 10_15_7', | |
| vendor : 'Google Inc.', | |
| renderer : 'ANGLE (Apple, ANGLE Metal Renderer: Apple M2, Unspecified Version)', | |
| vendorUnmasked : 'Apple', | |
| rendererUnmasked: 'Apple M2', | |
| hardwareConcurrency: 8, | |
| deviceMemory : 8, | |
| maxTouchPoints : 0, | |
| screenRes : [2560, 1664], | |
| colorDepth : 30, | |
| pixelRatio : 2, | |
| timezone : 'America/Mexico_City', | |
| language : 'es-MX', | |
| fonts : ['Arial', 'Courier New', 'Geneva', 'Georgia', 'Helvetica', 'Monaco', 'Times New Roman', 'Verdana'], | |
| plugins : ['Chrome PDF Plugin', 'Chrome PDF Viewer'], | |
| chromeVersion : '120.0.6099.129', | |
| userAgent : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', | |
| }, | |
| ]; | |
| // ββ Generar identidad de sesiΓ³n (una sola vez, CONSISTENTE toda la sesiΓ³n) βββ | |
| let _sessionIdentity = null; | |
| export function generateSessionIdentity(seed = null) { | |
| if (_sessionIdentity && !seed) return _sessionIdentity; | |
| // Seleccionar configuraciΓ³n base (misma para toda la sesiΓ³n) | |
| const configIndex = seed | |
| ? parseInt(createHash('md5').update(seed).digest('hex').slice(0, 2), 16) % REAL_DEVICE_CONFIGS.length | |
| : Math.floor(Math.random() * REAL_DEVICE_CONFIGS.length); | |
| const base = REAL_DEVICE_CONFIGS[configIndex]; | |
| // AΓ±adir variaciΓ³n SUTIL y CONSISTENTE (mismos valores toda la sesiΓ³n) | |
| const sessionSeed = seed || Date.now().toString(); | |
| const noise = (str, range) => { | |
| const h = parseInt(createHash('md5').update(str + sessionSeed).digest('hex').slice(0, 4), 16); | |
| return (h % (range * 2)) - range; | |
| }; | |
| _sessionIdentity = { | |
| ...base, | |
| // PequeΓ±a variaciΓ³n en resoluciΓ³n de pantalla (Β±8px) β realista | |
| screenRes: [ | |
| base.screenRes[0] + noise('sw', 8), | |
| base.screenRes[1] + noise('sh', 8), | |
| ], | |
| // VariaciΓ³n en memoria (+/- una banda) β realista | |
| deviceMemory: base.deviceMemory, | |
| // Canvas noise value β fijo para toda la sesiΓ³n (no cambia entre pΓ‘ginas) | |
| canvasNoise: noise('canvas', 3) * 0.001, | |
| audioNoise : noise('audio', 5) * 0.0001, | |
| sessionId : sessionSeed, | |
| }; | |
| return _sessionIdentity; | |
| } | |
| export function resetSessionIdentity() { | |
| _sessionIdentity = null; | |
| } | |
| // ββ Script de inyecciΓ³n completo βββββββββββββββββββββββββββββββββββββββββββββ | |
| // Se inyecta ANTES de que cualquier script de la pΓ‘gina se ejecute | |
| export function generateStealthScript(identity) { | |
| const id = identity || generateSessionIdentity(); | |
| return ` | |
| (function() { | |
| 'use strict'; | |
| // ββ 1. Ocultar webdriver completamente βββββββββββββββββββββββββββββββββββββ | |
| Object.defineProperty(navigator, 'webdriver', { get: () => undefined, configurable: false }); | |
| delete window.navigator.__proto__.webdriver; | |
| // ββ 2. Chrome runtime y permisos (ausentes en headless real) ββββββββββββββ | |
| if (!window.chrome) { | |
| window.chrome = { | |
| app: { isInstalled: false, getDetails: function() {}, getIsInstalled: function() {}, runningState: function() { return 'cannot_run'; } }, | |
| runtime: { | |
| onMessage: { addListener: function() {}, removeListener: function() {}, hasListeners: function() { return false; } }, | |
| connect: function() { return { onMessage: { addListener: function() {} }, onDisconnect: { addListener: function() {} }, postMessage: function() {} }; }, | |
| sendMessage: function() {}, | |
| id: undefined, | |
| }, | |
| loadTimes: function() { return { commitLoadTime: ${Date.now() / 1000 - 1.2 - Math.random()}, connectionInfo: 'http/1.1', finishDocumentLoadTime: 0, finishLoadTime: 0, firstPaintAfterLoadTime: 0, firstPaintTime: 0, navigationType: 'Other', npnNegotiatedProtocol: 'unknown', requestTime: ${Date.now() / 1000 - 1.5 - Math.random()}, startLoadTime: ${Date.now() / 1000 - 1.4 - Math.random()}, wasAlternateProtocolAvailable: false, wasFetchedViaSpdy: false, wasNpnNegotiated: false }; }, | |
| csi: function() { return { onloadT: ${Date.now() - 800}, pageT: 1500, startE: ${Date.now() - 1200}, tran: 15 }; }, | |
| }; | |
| } | |
| // ββ 3. Navigator properties β CONSISTENTES con el perfil βββββββββββββββββ | |
| const navigatorProps = { | |
| platform : '${id.platform}', | |
| vendor : '${id.vendor}', | |
| hardwareConcurrency: ${id.hardwareConcurrency}, | |
| deviceMemory : ${id.deviceMemory}, | |
| maxTouchPoints : ${id.maxTouchPoints}, | |
| languages : ['${id.language}', '${id.language.split('-')[0]}', 'en'], | |
| language : '${id.language}', | |
| plugins : { length: ${id.plugins.length} }, | |
| mimeTypes : { length: 4 }, | |
| doNotTrack : null, | |
| }; | |
| for (const [key, value] of Object.entries(navigatorProps)) { | |
| try { | |
| Object.defineProperty(navigator, key, { get: () => value, configurable: true }); | |
| } catch(e) {} | |
| } | |
| // ββ 4. Canvas fingerprint β ruido CONSISTENTE (mismo en cada carga) ββββββββ | |
| // CRΓTICO: NO aleatorio en cada llamada, sino fijo para la sesiΓ³n | |
| const CANVAS_NOISE = ${id.canvasNoise}; | |
| const _origToDataURL = HTMLCanvasElement.prototype.toDataURL; | |
| const _origGetImageData = CanvasRenderingContext2D.prototype.getImageData; | |
| HTMLCanvasElement.prototype.toDataURL = function(type, quality) { | |
| const result = _origToDataURL.apply(this, arguments); | |
| // Solo aΓ±adir ruido si parece fingerprinting (canvas pequeΓ±o) | |
| if (this.width <= 300 && this.height <= 150) { | |
| try { | |
| const ctx = this.getContext('2d'); | |
| if (ctx) { | |
| const id = ctx.getImageData(0, 0, this.width, this.height); | |
| // Ruido imperceptible pero consistente | |
| for (let i = 0; i < id.data.length; i += 4) { | |
| id.data[i] = Math.max(0, Math.min(255, id.data[i] + Math.round(CANVAS_NOISE * 255))); | |
| id.data[i+1] = Math.max(0, Math.min(255, id.data[i+1] + Math.round(CANVAS_NOISE * 127))); | |
| } | |
| ctx.putImageData(id, 0, 0); | |
| } | |
| } catch(e) {} | |
| } | |
| return _origToDataURL.apply(this, arguments); | |
| }; | |
| // ββ 5. WebGL fingerprint β valores REALES del perfil βββββββββββββββββββββ | |
| const _origGetParam = WebGLRenderingContext.prototype.getParameter; | |
| WebGLRenderingContext.prototype.getParameter = function(parameter) { | |
| if (parameter === 0x9245) return '${id.vendorUnmasked}'; // UNMASKED_VENDOR_WEBGL | |
| if (parameter === 0x9246) return '${id.rendererUnmasked}'; // UNMASKED_RENDERER_WEBGL | |
| if (parameter === 0x1F00) return '${id.vendor}'; // VENDOR | |
| if (parameter === 0x1F01) return '${id.renderer}'; // RENDERER | |
| return _origGetParam.apply(this, arguments); | |
| }; | |
| // WebGL2 tambiΓ©n | |
| try { | |
| const _origGetParam2 = WebGL2RenderingContext.prototype.getParameter; | |
| WebGL2RenderingContext.prototype.getParameter = function(parameter) { | |
| if (parameter === 0x9245) return '${id.vendorUnmasked}'; | |
| if (parameter === 0x9246) return '${id.rendererUnmasked}'; | |
| if (parameter === 0x1F00) return '${id.vendor}'; | |
| if (parameter === 0x1F01) return '${id.renderer}'; | |
| return _origGetParam2.apply(this, arguments); | |
| }; | |
| } catch(e) {} | |
| // ββ 6. Audio fingerprint β ruido consistente ββββββββββββββββββββββββββββββ | |
| const AUDIO_NOISE = ${id.audioNoise}; | |
| try { | |
| const _origGetChanData = AudioBuffer.prototype.getChannelData; | |
| AudioBuffer.prototype.getChannelData = function(channel) { | |
| const data = _origGetChanData.apply(this, arguments); | |
| for (let i = 0; i < data.length; i += 100) { | |
| data[i] += AUDIO_NOISE; | |
| } | |
| return data; | |
| }; | |
| } catch(e) {} | |
| // ββ 7. Timing attacks β usar valores realistas ββββββββββββββββββββββββββββ | |
| const _origNow = Date.now; | |
| const timeOffset = ${Math.floor(Math.random() * 200) - 100}; | |
| // Solo aΓ±adir offset mΓnimo para no romper lΓ³gica de timers | |
| // Date.now = function() { return _origNow() + timeOffset; }; // deshabilitado: rompe demasiado | |
| // ββ 8. Permissions API β igual que Chrome real ββββββββββββββββββββββββββββ | |
| if (navigator.permissions) { | |
| const _origQuery = navigator.permissions.query.bind(navigator.permissions); | |
| navigator.permissions.query = async function(params) { | |
| if (params.name === 'notifications') return { state: Notification.permission, onchange: null }; | |
| return _origQuery(params); | |
| }; | |
| } | |
| // ββ 9. Screen properties consistentes ββββββββββββββββββββββββββββββββββββ | |
| try { | |
| Object.defineProperty(screen, 'width', { get: () => ${id.screenRes[0]}, configurable: true }); | |
| Object.defineProperty(screen, 'height', { get: () => ${id.screenRes[1]}, configurable: true }); | |
| Object.defineProperty(screen, 'availWidth', { get: () => ${id.screenRes[0]}, configurable: true }); | |
| Object.defineProperty(screen, 'availHeight', { get: () => ${id.screenRes[1] - 40}, configurable: true }); | |
| Object.defineProperty(screen, 'colorDepth', { get: () => ${id.colorDepth}, configurable: true }); | |
| Object.defineProperty(window, 'devicePixelRatio', { get: () => ${id.pixelRatio}, configurable: true }); | |
| } catch(e) {} | |
| // ββ 10. Eliminar rastros de Playwright/CDP ββββββββββββββββββββββββββββββββ | |
| try { | |
| delete window.cdc_adoQpoasnfa76pfcZLmcfl_Array; | |
| delete window.cdc_adoQpoasnfa76pfcZLmcfl_Promise; | |
| delete window.cdc_adoQpoasnfa76pfcZLmcfl_Symbol; | |
| delete window.__playwright; | |
| delete window.__pw_manual; | |
| delete window.__PW_inspect; | |
| } catch(e) {} | |
| // ββ 11. iframe contentWindow stealth βββββββββββββββββββββββββββββββββββββ | |
| // Anti-bot checks en iframes (como Cloudflare Turnstile) | |
| const _origContentWindow = Object.getOwnPropertyDescriptor(HTMLIFrameElement.prototype, 'contentWindow'); | |
| if (_origContentWindow) { | |
| Object.defineProperty(HTMLIFrameElement.prototype, 'contentWindow', { | |
| get: function() { | |
| const cw = _origContentWindow.get.call(this); | |
| if (!cw) return null; | |
| try { | |
| Object.defineProperty(cw.navigator, 'webdriver', { get: () => undefined, configurable: true }); | |
| } catch(e) {} | |
| return cw; | |
| }, | |
| configurable: true, | |
| }); | |
| } | |
| // ββ 12. Ocultar stack traces de automation ββββββββββββββββββββββββββββββββ | |
| // Algunos sites detectan errores con stack traces de Playwright | |
| const _origPrepareStackTrace = Error.prepareStackTrace; | |
| Error.prepareStackTrace = function(err, stack) { | |
| const filtered = stack.filter(frame => { | |
| const name = frame.getFileName?.() || ''; | |
| return !name.includes('playwright') && !name.includes('puppeteer') && !name.includes('@playwright'); | |
| }); | |
| return _origPrepareStackTrace ? _origPrepareStackTrace(err, filtered) : filtered.map(f => f.toString()).join('\n'); | |
| }; | |
| window._stealthApplied = true; | |
| window._sessionId = '${id.sessionId}'; | |
| })(); | |
| `; | |
| } | |
| export function getIdentityStats(identity) { | |
| const id = identity || generateSessionIdentity(); | |
| return { | |
| platform : id.platform, | |
| renderer : id.rendererUnmasked, | |
| screen : id.screenRes.join('x'), | |
| language : id.language, | |
| userAgent : id.userAgent.slice(0, 60) + '...', | |
| }; | |
| } | |