UNPKG

@fanboynz/network-scanner

Version:

A Puppeteer-based network scanner for analyzing web traffic, generating adblock filter rules, and identifying third-party requests. Features include fingerprint spoofing, Cloudflare bypass, content analysis with curl/grep, and multiple output formats.

1,186 lines (1,078 loc) 91.2 kB
// === Enhanced Fingerprint Protection Module - Puppeteer 23.x Compatible === // This module handles advanced browser fingerprint spoofing, user agent changes, // and comprehensive bot detection evasion techniques. //const { applyErrorSuppression } = require('./error-suppression'); // Default values for fingerprint spoofing if not set to 'random' const DEFAULT_PLATFORM = 'Win32'; const DEFAULT_TIMEZONE = 'America/New_York'; // Deterministic random generator seeded by domain string // Same domain always produces the same sequence of values function seededRandom(seed) { let h = 0; for (let i = 0; i < seed.length; i++) { h = ((h << 5) - h + seed.charCodeAt(i)) | 0; } return () => { h = (h * 1664525 + 1013904223) | 0; return (h >>> 0) / 4294967296; }; } // Cache fingerprints per domain so reloads and multi-page visits stay consistent const _fingerprintCache = new Map(); const FINGERPRINT_CACHE_MAX = 500; // Type-specific property spoofing functions for monomorphic optimization // Built-in properties that should not be modified const BUILT_IN_PROPERTIES = new Set([ 'href', 'origin', 'protocol', 'host', 'hostname', 'port', 'pathname', 'search', 'hash', 'constructor', 'prototype', '__proto__', 'toString', 'valueOf', 'assign', 'reload', 'replace' ]); // User agent collections with latest versions const USER_AGENT_COLLECTIONS = Object.freeze(new Map([ ['chrome', "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"], ['chrome_mac', "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"], ['chrome_linux', "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"], ['firefox', "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:148.0) Gecko/20100101 Firefox/148.0"], ['firefox_mac', "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:148.0) Gecko/20100101 Firefox/148.0"], ['firefox_linux', "Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0"], ['safari', "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.6 Safari/605.1.15"] ])); // GPU pool — realistic vendor/renderer combos per OS (used for WebGL spoofing) const GPU_POOL = { windows: [ { vendor: 'Google Inc. (ANGLE)', renderer: 'ANGLE (Intel(R) UHD Graphics 630 Direct3D11 vs_5_0 ps_5_0, D3D11)' }, { vendor: 'Google Inc. (ANGLE)', renderer: 'ANGLE (Intel(R) UHD Graphics 770 Direct3D11 vs_5_0 ps_5_0, D3D11)' }, { vendor: 'Google Inc. (ANGLE)', renderer: 'ANGLE (Intel(R) Iris(R) Xe Graphics Direct3D11 vs_5_0 ps_5_0, D3D11)' }, { vendor: 'Google Inc. (ANGLE)', renderer: 'ANGLE (NVIDIA GeForce GTX 1650 Direct3D11 vs_5_0 ps_5_0, D3D11)' }, { vendor: 'Google Inc. (ANGLE)', renderer: 'ANGLE (NVIDIA GeForce GTX 1060 6GB Direct3D11 vs_5_0 ps_5_0, D3D11)' }, { vendor: 'Google Inc. (ANGLE)', renderer: 'ANGLE (NVIDIA GeForce RTX 3060 Direct3D11 vs_5_0 ps_5_0, D3D11)' }, { vendor: 'Google Inc. (ANGLE)', renderer: 'ANGLE (NVIDIA GeForce RTX 4060 Direct3D11 vs_5_0 ps_5_0, D3D11)' }, { vendor: 'Google Inc. (ANGLE)', renderer: 'ANGLE (AMD Radeon RX 580 Direct3D11 vs_5_0 ps_5_0, D3D11)' }, { vendor: 'Google Inc. (ANGLE)', renderer: 'ANGLE (AMD Radeon(TM) Graphics Direct3D11 vs_5_0 ps_5_0, D3D11)' }, { vendor: 'Google Inc. (ANGLE)', renderer: 'ANGLE (AMD Radeon RX 6600 XT Direct3D11 vs_5_0 ps_5_0, D3D11)' }, ], mac: [ { vendor: 'Apple', renderer: 'Apple M1' }, { vendor: 'Apple', renderer: 'Apple M1 Pro' }, { vendor: 'Apple', renderer: 'Apple M2' }, { vendor: 'Apple', renderer: 'Apple M3' }, { vendor: 'Intel Inc.', renderer: 'Intel(R) UHD Graphics 630' }, { vendor: 'Intel Inc.', renderer: 'Intel(R) Iris(TM) Plus Graphics 655' }, { vendor: 'Intel Inc.', renderer: 'Intel(R) Iris(TM) Plus Graphics' }, ], linux: [ { vendor: 'Google Inc. (ANGLE)', renderer: 'ANGLE (Intel(R) UHD Graphics 630, Mesa 23.2.1, OpenGL 4.6)' }, { vendor: 'Google Inc. (ANGLE)', renderer: 'ANGLE (Intel(R) UHD Graphics 770, Mesa 24.0.3, OpenGL 4.6)' }, { vendor: 'Google Inc. (ANGLE)', renderer: 'ANGLE (NVIDIA GeForce GTX 1080, NVIDIA 535.183.01, OpenGL 4.6.0)' }, { vendor: 'Google Inc. (ANGLE)', renderer: 'ANGLE (NVIDIA GeForce RTX 3070, NVIDIA 545.29.06, OpenGL 4.6.0)' }, { vendor: 'Google Inc. (ANGLE)', renderer: 'ANGLE (AMD Radeon RX 580, Mesa 23.2.1, OpenGL 4.6)' }, ] }; /** * Select a GPU from the pool based on user agent string. * Called once per browser session so the GPU stays consistent across page loads. */ function selectGpuForUserAgent(userAgentString) { let osKey = 'windows'; if (userAgentString && (userAgentString.includes('Macintosh') || userAgentString.includes('Mac OS X'))) osKey = 'mac'; else if (userAgentString && (userAgentString.includes('X11; Linux') || userAgentString.includes('Ubuntu'))) osKey = 'linux'; const pool = GPU_POOL[osKey]; return pool[Math.floor(Math.random() * pool.length)]; } /** * Checks if an error is a session/protocol closed error (common during page navigation) */ function isSessionClosedError(err) { const msg = err.message; return msg.includes('Session closed') || msg.includes('addScriptToEvaluateOnNewDocument timed out') || msg.includes('Target closed') || msg.includes('Protocol error') || err.name === 'ProtocolError' || msg.includes('detached Frame') || msg.includes('Navigating frame was detached') || msg.includes('Cannot find context') || msg.includes('Execution context was destroyed'); } /** * Safely defines a property with comprehensive error handling */ function safeDefineProperty(target, property, descriptor, options = {}) { if (BUILT_IN_PROPERTIES.has(property)) { if (options.debug) console.log(`[fingerprint] Skipping built-in property: ${property}`); return false; } try { const existing = Object.getOwnPropertyDescriptor(target, property); if (existing?.configurable === false) { if (options.debug) console.log(`[fingerprint] Cannot modify non-configurable: ${property}`); return false; } Object.defineProperty(target, property, descriptor); return true; } catch (err) { if (options.debug) console.log(`[fingerprint] Failed to define ${property}: ${err.message}`); return false; } } /** * Safely executes spoofing operations with error handling */ function safeSpoofingExecution(spoofFunction, description, options = {}) { try { spoofFunction(); return true; } catch (err) { if (options.debug) console.log(`[fingerprint] ${description} failed: ${err.message}`); return false; } } /** * Generates realistic screen resolutions based on common monitor sizes */ function getRealisticScreenResolution() { const commonResolutions = [ { width: 1920, height: 1080 }, { width: 1366, height: 768 }, { width: 1440, height: 900 }, { width: 1536, height: 864 }, { width: 1600, height: 900 }, { width: 2560, height: 1440 }, { width: 1280, height: 720 }, { width: 3440, height: 1440 } ]; return commonResolutions[Math.floor(Math.random() * commonResolutions.length)]; } /** * Generates randomized but realistic browser fingerprint values * When domain is provided, values are deterministic per-domain (consistent across reloads) */ function generateRealisticFingerprint(userAgent, domain = '') { // Return cached fingerprint if same domain visited again if (domain) { const cached = _fingerprintCache.get(domain); if (cached) return cached; } // Use seeded random for consistency, or Math.random if no domain const rand = domain ? seededRandom(domain) : Math.random; // Determine OS from user agent let osType = 'windows'; if (userAgent.includes('Macintosh') || userAgent.includes('Mac OS X')) { osType = 'mac'; } else if (userAgent.includes('X11; Linux') || userAgent.includes('Ubuntu')) { osType = 'linux'; } // Generate OS-appropriate hardware specs const profiles = { windows: { deviceMemory: [8, 16], // Common Windows configurations hardwareConcurrency: [4, 6, 8], // Typical consumer CPUs platform: 'Win32', timezone: 'America/New_York', language: 'en-US', resolutions: [ { width: 1920, height: 1080 }, { width: 2560, height: 1440 }, { width: 1366, height: 768 } ] }, mac: { deviceMemory: [8, 16], // MacBook/iMac typical configs hardwareConcurrency: [8, 10], // Apple Silicon M1/M2 cores platform: 'MacIntel', timezone: 'America/Los_Angeles', language: 'en-US', resolutions: [ { width: 2560, height: 1600 }, // MacBook Pro { width: 3840, height: 2160 }, // iMac 4K { width: 1440, height: 900 } // MacBook Air ] }, linux: { deviceMemory: [8, 16], hardwareConcurrency: [4, 8, 12], // Wide variety on Linux platform: 'Linux x86_64', timezone: 'America/New_York', language: 'en-US', resolutions: [ { width: 1920, height: 1080 }, { width: 2560, height: 1440 }, { width: 1600, height: 900 } ] } }; const profile = profiles[osType]; const resolution = profile.resolutions[Math.floor(rand() * profile.resolutions.length)]; const fingerprint = { deviceMemory: profile.deviceMemory[Math.floor(rand() * profile.deviceMemory.length)], hardwareConcurrency: profile.hardwareConcurrency[Math.floor(rand() * profile.hardwareConcurrency.length)], screen: { width: resolution.width, height: resolution.height, availWidth: resolution.width, availHeight: resolution.height - 40, colorDepth: 24, pixelDepth: 24 }, platform: profile.platform, timezone: profile.timezone, language: profile.language, cookieEnabled: true, doNotTrack: null // Most users don't enable DNT }; // Cache for this domain if (domain) { if (_fingerprintCache.size >= FINGERPRINT_CACHE_MAX) { _fingerprintCache.delete(_fingerprintCache.keys().next().value); } _fingerprintCache.set(domain, fingerprint); } return fingerprint; } /** * Validates page state before script injection to avoid timeouts */ async function validatePageForInjection(page, currentUrl, forceDebug) { try { if (!page || page.isClosed()) return false; if (!page.browser().isConnected()) { if (forceDebug) console.log(`[debug] Page validation failed - browser disconnected: ${currentUrl}`); return false; } await Promise.race([ page.evaluate(() => document.readyState || 'loading'), new Promise((_, reject) => setTimeout(() => reject(new Error('Page evaluation timeout')), 1500)) ]); return true; } catch (validationErr) { if (forceDebug) console.log(`[debug] Page validation failed - ${validationErr.message}: ${currentUrl}`); return false; } } /** * Enhanced user agent spoofing with stealth protection */ async function applyUserAgentSpoofing(page, siteConfig, forceDebug, currentUrl) { if (!siteConfig.userAgent) return; if (forceDebug) console.log(`[debug] User agent spoofing: ${siteConfig.userAgent}`); // Browser connection check try { if (!page.browser().isConnected() || page.isClosed()) return; if (page.browser().process()?.killed) return; } catch { return; } // Validate page state before injection if (!(await validatePageForInjection(page, currentUrl, forceDebug))) return; const ua = USER_AGENT_COLLECTIONS.get(siteConfig.userAgent.toLowerCase()); if (ua) { // FIX: Wrap setUserAgent in try-catch to handle race condition try { await page.setUserAgent(ua); } catch (uaErr) { if (forceDebug) console.log(`[debug] Could not set user agent - page closed: ${currentUrl}`); return; } if (forceDebug) console.log(`[debug] Applying stealth protection for ${currentUrl}`); try { // Select GPU once per session — stays consistent across all page loads const selectedGpu = selectGpuForUserAgent(ua); if (forceDebug) console.log(`[debug] Selected GPU: ${selectedGpu.vendor} / ${selectedGpu.renderer}`); await page.evaluateOnNewDocument((userAgent, debugEnabled, gpuConfig) => { // Apply inline error suppression first (function() { const originalConsoleError = console.error; const originalWindowError = window.onerror; function shouldSuppressFingerprintError(message) { const patterns = [ /\.closest is not a function/i, /\.querySelector is not a function/i, /\.addEventListener is not a function/i, /Cannot read propert(y|ies) of null \\(reading 'fp'\\)/i, /Cannot read propert(y|ies) of undefined \\(reading 'fp'\\)/i, /Cannot redefine property: href/i, /Cannot redefine property: __webdriver_script_func/i, /Cannot redefine property: webdriver/i, /Cannot read propert(y|ies) of undefined \\(reading 'toLowerCase'\\)/i, /\\.toLowerCase is not a function/i, /fp is not defined/i, /fingerprint is not defined/i, /FingerprintJS is not defined/i, /\\$ is not defined/i, /jQuery is not defined/i, /_ is not defined/i, /Failed to load resource.*server responded with a status of [45]\\d{2}/i, /Failed to fetch/i, /(webdriver|callPhantom|_phantom|__nightmare|_selenium) is not defined/i, /Failed to execute 'observe' on 'IntersectionObserver'.*parameter 1 is not of type 'Element'/i, /tz check/i, /new window\\.Error.*<anonymous>/i, /Failed to load resource.*server responded with a status of 40[34]/i, /Blocked script execution in 'about:blank'.*sandboxed.*allow-scripts/i, /Page JavaScript error:/i, /^[a-zA-Z0-9_$]+\[.*\]\s+is not a function/i, /^[a-zA-Z0-9_$]+\(.*\)\s+is not a function/i, /^[a-zA-Z0-9_$]+\.[a-zA-Z0-9_$]+.*is not a function/i, /Failed to load resource/i, /is not defined/i, /is not a function/i ]; return patterns.some(pattern => pattern.test(String(message || ''))); } console.error = function(...args) { const message = args.join(' '); if (shouldSuppressFingerprintError(message)) { if (debugEnabled) console.log("[fingerprint] Suppressed error:", message); return; } return originalConsoleError.apply(this, arguments); }; window.onerror = function(message, source, lineno, colno, error) { if (shouldSuppressFingerprintError(message)) { if (debugEnabled) console.log("[fingerprint] Suppressed window error:", message); return true; } if (originalWindowError) { return originalWindowError.apply(this, arguments); } return false; }; })(); // Function.prototype.toString protection — make spoofed functions appear native // Must be installed BEFORE any property overrides so all spoofs are protected const nativeFunctionStore = new WeakMap(); const originalToString = Function.prototype.toString; function maskAsNative(fn, nativeName) { if (typeof fn === 'function') { nativeFunctionStore.set(fn, nativeName || fn.name || ''); } return fn; } Function.prototype.toString = function() { if (nativeFunctionStore.has(this)) { return `function ${nativeFunctionStore.get(this)}() { [native code] }`; } return originalToString.call(this); }; // Protect the toString override itself nativeFunctionStore.set(Function.prototype.toString, 'toString'); // Create safe property definition helper function safeDefinePropertyLocal(target, property, descriptor) { const builtInProps = new Set(['href', 'origin', 'protocol', 'host', 'hostname', 'port', 'pathname', 'search', 'hash', 'constructor', 'prototype', '__proto__', 'toString', 'valueOf', 'assign', 'reload', 'replace']); if (builtInProps.has(property)) { if (debugEnabled) console.log(`[fingerprint] Skipping built-in property: ${property}`); return false; } try { const existing = Object.getOwnPropertyDescriptor(target, property); if (existing?.configurable === false) { if (debugEnabled) console.log(`[fingerprint] Cannot modify non-configurable: ${property}`); return false; } Object.defineProperty(target, property, { ...descriptor, configurable: true }); return true; } catch (err) { if (debugEnabled) console.log(`[fingerprint] Failed to define ${property}: ${err.message}`); return false; } } // Add monomorphic spoofing functions for page context function spoofNavigatorProperties(navigator, properties) { for (const [prop, descriptor] of Object.entries(properties)) { safeDefinePropertyLocal(navigator, prop, descriptor); } } function spoofScreenProperties(screen, properties) { for (const [prop, descriptor] of Object.entries(properties)) { safeDefinePropertyLocal(screen, prop, descriptor); } } // Safe execution wrapper function safeExecute(fn, description) { try { fn(); return true; } catch (err) { if (debugEnabled) console.log(`[fingerprint] ${description} failed: ${err.message}`); return false; } } // Remove webdriver properties // safeExecute(() => { // In real Chrome, webdriver lives on Navigator.prototype, not the instance. // Override it there so Object.getOwnPropertyDescriptor(navigator, 'webdriver') returns undefined. try { delete navigator.webdriver; } catch (e) {} Object.defineProperty(Navigator.prototype, 'webdriver', { get: () => false, configurable: true, enumerable: true }); }, 'webdriver removal'); // Remove automation properties // safeExecute(() => { const automationProps = [ 'callPhantom', '_phantom', '__nightmare', '_selenium', '__selenium_unwrapped', '__webdriver_evaluate', '__driver_evaluate', '__webdriver_script_function', '__fxdriver_evaluate', '__fxdriver_unwrapped', '__webdriver_script_fn', 'phantomjs', '_Selenium_IDE_Recorder', 'callSelenium', '_selenium', '__phantomas', '__selenium_evaluate', '__driver_unwrapped', 'webdriver-evaluate', '__webdriverFunc', 'driver-evaluate', '__driver-evaluate', '__selenium-evaluate', 'spawn', 'emit', 'Buffer', 'domAutomation', 'domAutomationController', 'cdc_adoQpoasnfa76pfcZLmcfl_JSON', 'cdc_adoQpoasnfa76pfcZLmcfl_Object', 'cdc_adoQpoasnfa76pfcZLmcfl_Proxy', 'cdc_adoQpoasnfa76pfcZLmcfl_Reflect', '$cdc_asdjflasutopfhvcZLmcfl_', '$chrome_asyncScriptInfo', '__$webdriverAsyncExecutor' ]; automationProps.forEach(prop => { try { delete window[prop]; delete navigator[prop]; safeDefinePropertyLocal(window, prop, { get: () => undefined }); safeDefinePropertyLocal(navigator, prop, { get: () => undefined }); } catch (e) {} }); }, 'automation properties removal'); // Simulate Chrome runtime // safeExecute(() => { if (!window.chrome) { window.chrome = {}; } // Add runtime if missing — headless Chrome has chrome object but no runtime if (!window.chrome.runtime) { window.chrome.runtime = { onConnect: { addListener: () => {}, removeListener: () => {} }, onMessage: { addListener: () => {}, removeListener: () => {} }, sendMessage: () => {}, connect: () => ({ onMessage: { addListener: () => {}, removeListener: () => {} }, postMessage: () => {}, disconnect: () => {} }), getManifest: () => ({ name: "Chrome", version: "146.0.0.0", manifest_version: 3, description: "Chrome Browser" }), getURL: (path) => `chrome-extension://invalid/${path}`, id: undefined, getPlatformInfo: (callback) => callback({ os: navigator.platform.includes('Win') ? 'win' : navigator.platform.includes('Mac') ? 'mac' : 'linux', arch: 'x86-64', nacl_arch: 'x86-64' }) }; Object.defineProperty(window.chrome.runtime, 'toString', { value: () => '[object Object]' }); } // Add app if missing if (!window.chrome.app) { window.chrome.app = { isInstalled: false, InstallState: { DISABLED: 'disabled', INSTALLED: 'installed', NOT_INSTALLED: 'not_installed' }, RunningState: { CANNOT_RUN: 'cannot_run', READY_TO_RUN: 'ready_to_run', RUNNING: 'running' }, getDetails: () => null, getIsInstalled: () => false }; } // Add storage if missing if (!window.chrome.storage) { window.chrome.storage = { local: { get: (keys, callback) => callback && callback({}), set: (items, callback) => callback && callback(), remove: (keys, callback) => callback && callback(), clear: (callback) => callback && callback() }, sync: { get: (keys, callback) => callback && callback({}), set: (items, callback) => callback && callback(), remove: (keys, callback) => callback && callback(), clear: (callback) => callback && callback() } }; } // Add loadTimes/csi if missing if (!window.chrome.loadTimes) { window.chrome.loadTimes = () => { const now = performance.now(); return { commitLoadTime: now - Math.random() * 1000, connectionInfo: 'http/1.1', finishDocumentLoadTime: now - Math.random() * 500, finishLoadTime: now - Math.random() * 100, navigationType: 'Navigation' }; }; } if (!window.chrome.csi) { window.chrome.csi = () => ({ onloadT: Date.now(), pageT: Math.random() * 1000, startE: Date.now() - Math.random() * 2000 }); } // Make chrome object non-enumerable to match real Chrome Object.defineProperty(window, 'chrome', { value: window.chrome, writable: false, enumerable: false, configurable: true }); // Add Chrome-specific globals that Cloudflare might check if (!window.external) { window.external = { AddSearchProvider: () => {}, IsSearchProviderInstalled: () => 0 }; } }, 'Chrome runtime simulation'); // Add realistic Chrome browser behavior // safeExecute(() => { // Remove Puppeteer-specific properties not covered in main automation cleanup delete window.__puppeteer_evaluation_script__; delete window.__runtime; delete window._asyncToGenerator; delete window.__puppeteer; delete window.__cdp; delete window.__REACT_DEVTOOLS_GLOBAL_HOOK__; // Simulate Chrome's performance observer if (window.PerformanceObserver) { try { const observer = new PerformanceObserver(() => {}); observer.observe({entryTypes: ['navigation']}); } catch(e) { // Silently ignore if PerformanceObserver fails } } // Ensure external object exists (don't overwrite chrome object) window.external = window.external || {}; }, 'realistic Chrome behavior'); // Spoof plugins based on user agent // safeExecute(() => { let plugins = []; if (userAgent.includes('Chrome')) { plugins = [ { name: 'Chrome PDF Plugin', description: 'Portable Document Format', filename: 'internal-pdf-viewer', length: 1, version: '' }, { name: 'Chrome PDF Viewer', description: '', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', length: 1, version: '' } ]; } else if (userAgent.includes('Firefox')) { plugins = [ { name: 'PDF.js', description: 'Portable Document Format', filename: 'internal-pdf-js', length: 2, version: '5.4.70' } ]; } else if (userAgent.includes('Safari')) { // Safari typically has no plugins in modern versions plugins = []; } // Create proper array-like object with enumerable indices and length // Create proper PluginArray-like object with required methods const pluginsArray = {}; plugins.forEach((plugin, index) => { pluginsArray[index] = plugin; }); // Ensure length property is properly defined Object.defineProperty(pluginsArray, 'length', { value: plugins.length, writable: false, enumerable: false, configurable: false }); // PluginArray methods that bot detectors check for pluginsArray.item = function(i) { return plugins[i] || null; }; pluginsArray.namedItem = function(name) { return plugins.find(p => p.name === name) || null; }; pluginsArray.refresh = function() {}; pluginsArray[Symbol.iterator] = function*() { for (const p of plugins) yield p; }; pluginsArray[Symbol.toStringTag] = 'PluginArray'; safeDefinePropertyLocal(navigator, 'plugins', { get: () => pluginsArray }); }, 'plugins spoofing'); // Spoof languages // safeExecute(() => { const languages = ['en-US', 'en']; const languageProps = { languages: { get: () => languages }, language: { get: () => languages[0] } }; spoofNavigatorProperties(navigator, languageProps); }, 'language spoofing'); // Spoof vendor information // safeExecute(() => { let vendor = 'Google Inc.'; let product = 'Gecko'; if (userAgent.includes('Firefox')) { vendor = ''; } else if (userAgent.includes('Safari') && !userAgent.includes('Chrome')) { vendor = 'Apple Computer, Inc.'; } const vendorProps = { vendor: { get: () => vendor }, product: { get: () => product } }; spoofNavigatorProperties(navigator, vendorProps); }, 'vendor/product spoofing'); // navigator.userAgentData — Chrome's Client Hints JS API // Detection scripts check this; headless may have it missing or inconsistent safeExecute(() => { if (!userAgent.includes('Chrome/')) return; // Only for Chrome UAs const chromeMatch = userAgent.match(/Chrome\/(\d+)/); const majorVersion = chromeMatch ? chromeMatch[1] : '145'; let platform = 'Windows'; let platformVersion = '15.0.0'; let architecture = 'x86'; let model = ''; let bitness = '64'; if (userAgent.includes('Macintosh') || userAgent.includes('Mac OS X')) { platform = 'macOS'; platformVersion = '13.5.0'; architecture = 'arm'; } else if (userAgent.includes('X11; Linux')) { platform = 'Linux'; platformVersion = '6.5.0'; architecture = 'x86'; } const brands = [ { brand: 'Not:A-Brand', version: '99' }, { brand: 'Google Chrome', version: majorVersion }, { brand: 'Chromium', version: majorVersion } ]; const uaData = { brands: brands, mobile: false, platform: platform, getHighEntropyValues: function(hints) { const result = { brands: brands, mobile: false, platform: platform, architecture: architecture, bitness: bitness, model: model, platformVersion: platformVersion, fullVersionList: [ { brand: 'Not:A-Brand', version: '99.0.0.0' }, { brand: 'Google Chrome', version: majorVersion + '.0.7632.160' }, { brand: 'Chromium', version: majorVersion + '.0.7632.160' } ] }; // Only return requested hints const filtered = {}; for (const hint of hints) { if (result.hasOwnProperty(hint)) filtered[hint] = result[hint]; } return Promise.resolve(filtered); }, toJSON: function() { return { brands: brands, mobile: false, platform: platform }; } }; Object.defineProperty(navigator, 'userAgentData', { get: () => uaData, configurable: true }); }, 'navigator.userAgentData spoofing'); // Enhanced OS fingerprinting protection based on actual user agent content // safeExecute(() => { let osType = 'windows'; let browserType = 'chrome'; // Detect OS from user agent string patterns if (userAgent.includes('Macintosh') || userAgent.includes('Mac OS X')) { osType = 'mac'; } else if (userAgent.includes('X11; Linux') || userAgent.includes('Ubuntu')) { osType = 'linux'; } else if (userAgent.includes('Windows NT')) { osType = 'windows'; } // Detect browser type if (userAgent.includes('Firefox/')) { browserType = 'firefox'; } else if (userAgent.includes('Safari/') && !userAgent.includes('Chrome/')) { browserType = 'safari'; } // Apply OS-specific navigator properties if (osType === 'windows') { if (browserType === 'firefox') { safeDefinePropertyLocal(navigator, 'oscpu', { get: () => 'Windows NT 10.0; Win64; x64' }); safeDefinePropertyLocal(navigator, 'buildID', { get: () => '20100101' }); } if (window.screen) { safeDefinePropertyLocal(window.screen, 'fontSmoothingEnabled', { get: () => true }); } } else if (osType === 'mac') { if (browserType === 'firefox') { safeDefinePropertyLocal(navigator, 'oscpu', { get: () => 'Intel Mac OS X 10.15' }); safeDefinePropertyLocal(navigator, 'buildID', { get: () => '20100101' }); } } else if (osType === 'linux') { if (browserType === 'firefox') { safeDefinePropertyLocal(navigator, 'oscpu', { get: () => 'Linux x86_64' }); safeDefinePropertyLocal(navigator, 'buildID', { get: () => '20100101' }); } } }, 'enhanced OS fingerprinting protection'); // Hardware concurrency spoofing (universal coverage) // safeExecute(() => { const spoofedCores = [4, 6, 8, 12][Math.floor(Math.random() * 4)]; const hardwareProps = { hardwareConcurrency: { get: () => spoofedCores } }; spoofNavigatorProperties(navigator, hardwareProps); }, 'hardware concurrency spoofing'); // Screen resolution fingerprinting protection // safeExecute(() => { // Common realistic resolutions to avoid fingerprinting const commonResolutions = [ { width: 1920, height: 1080 }, { width: 2560, height: 1440 }, { width: 3840, height: 2160 }, { width: 1280, height: 720 }, { width: 1366, height: 768 }, { width: 1440, height: 900 }, { width: 1536, height: 864 } ]; const resolution = commonResolutions[Math.floor(Math.random() * commonResolutions.length)]; const screenProps = { width: { get: () => resolution.width }, height: { get: () => resolution.height }, availWidth: { get: () => resolution.width }, availHeight: { get: () => resolution.height - 40 } }; spoofScreenProperties(window.screen, screenProps); }, 'screen resolution protection'); // Spoof MIME types // safeExecute(() => { let mimeTypes = []; if (userAgent.includes('Chrome')) { mimeTypes = [ { type: 'application/pdf', description: 'Portable Document Format', suffixes: 'pdf' }, { type: 'application/x-google-chrome-pdf', description: 'Portable Document Format', suffixes: 'pdf' } ]; } safeDefinePropertyLocal(navigator, 'mimeTypes', { get: () => mimeTypes }); }, 'mimeTypes spoofing'); // Enhanced Error.stack protection for CDP detection safeExecute(() => { const OriginalError = window.Error; window.Error = function(...args) { const error = new OriginalError(...args); const originalStack = error.stack; Object.defineProperty(error, 'stack', { get: function() { if (typeof originalStack === 'string') { return originalStack .replace(/.*puppeteer.*\n?/gi, '') .replace(/.*__puppeteer_evaluation_script__.*\n?/gi, '') .replace(/.*evaluateOnNewDocument.*\n?/gi, '') .replace(/.*chrome-devtools.*\n?/gi, '') .replace(/.*webdriver.*\n?/gi, '') .replace(/.*automation.*\n?/gi, '') .trim() || `${this.name || 'Error'}: ${this.message || ''}\n at unknown location`; } return originalStack; }, configurable: true }); return error; }; window.Error.prototype = OriginalError.prototype; Object.setPrototypeOf(window.Error, OriginalError); // Copy static properties ['captureStackTrace', 'stackTraceLimit', 'prepareStackTrace'].forEach(prop => { if (OriginalError[prop]) { try { window.Error[prop] = OriginalError[prop]; } catch (e) {} } }); }, 'Error stack protection'); // Create fingerprinting mock objects safeExecute(() => { const mockResult = { visitorId: 'mock_visitor_' + Math.random().toString(36).substr(2, 9), confidence: { score: 0.99 }, components: { screen: { value: { width: 1920, height: 1080 } }, timezone: { value: 'America/New_York' } } }; window.fp = window.fp || { getResult: (callback) => callback ? setTimeout(() => callback(mockResult), 0) : mockResult, get: (callback) => Promise.resolve(mockResult), load: () => Promise.resolve(window.fp) }; window.FingerprintJS = window.FingerprintJS || { load: () => Promise.resolve({ get: () => Promise.resolve(mockResult) }) }; window.ClientJS = window.ClientJS || function() { this.getFingerprint = () => 'mock_fingerprint_' + Math.random().toString(36).substr(2, 9); }; }, 'fingerprinting mocks'); // GPU identity — selected once per browser session, passed from Node.js const GPU_VENDOR = gpuConfig.vendor; const GPU_RENDERER = gpuConfig.renderer; if (debugEnabled) console.log(`[fingerprint] GPU: ${GPU_VENDOR} / ${GPU_RENDERER}`); // WebGL spoofing // safeExecute(() => { // Enhanced WebGL fingerprinting protection const webglParams = { 37445: GPU_VENDOR, // VENDOR 37446: GPU_RENDERER, // RENDERER 7936: 'WebGL 1.0 (OpenGL ES 2.0 Chromium)', // VERSION 35724: 'WebGL GLSL ES 1.0 (OpenGL ES GLSL ES 1.00 Chromium)', // SHADING_LANGUAGE_VERSION 34076: 16384, // MAX_TEXTURE_SIZE 34024: 16384, // MAX_CUBE_MAP_TEXTURE_SIZE 34930: new Float32Array([1, 1]), // ALIASED_LINE_WIDTH_RANGE 33901: new Float32Array([0, 1]), // ALIASED_POINT_SIZE_RANGE 35660: 16, // MAX_VERTEX_ATTRIBS 35661: 16, // MAX_VERTEX_UNIFORM_VECTORS 35659: 16, // MAX_VARYING_VECTORS 35663: 16, // MAX_FRAGMENT_UNIFORM_VECTORS 36347: 4096, // MAX_RENDERBUFFER_SIZE 34852: 32, // MAX_COMBINED_TEXTURE_IMAGE_UNITS 2978: new Int32Array([0, 0, 1920, 1080]), // VIEWPORT 3379: new Int32Array([0, 0, 1920, 1080]) // SCISSOR_BOX }; if (window.WebGLRenderingContext) { const getParameter = WebGLRenderingContext.prototype.getParameter; WebGLRenderingContext.prototype.getParameter = function(parameter) { if (webglParams.hasOwnProperty(parameter)) { return webglParams[parameter]; } return getParameter.call(this, parameter); }; // Intercept getExtension to control WEBGL_debug_renderer_info const getExtension = WebGLRenderingContext.prototype.getExtension; WebGLRenderingContext.prototype.getExtension = function(name) { if (name === 'WEBGL_debug_renderer_info') { return { UNMASKED_VENDOR_WEBGL: 37445, UNMASKED_RENDERER_WEBGL: 37446 }; } return getExtension.call(this, name); }; // Spoof supported extensions const getSupportedExtensions = WebGLRenderingContext.prototype.getSupportedExtensions; WebGLRenderingContext.prototype.getSupportedExtensions = function() { return [ 'ANGLE_instanced_arrays', 'EXT_blend_minmax', 'EXT_color_buffer_half_float', 'EXT_disjoint_timer_query', 'EXT_float_blend', 'EXT_frag_depth', 'EXT_shader_texture_lod', 'EXT_texture_compression_rgtc', 'EXT_texture_filter_anisotropic', 'WEBKIT_EXT_texture_filter_anisotropic', 'EXT_sRGB', 'OES_element_index_uint', 'OES_fbo_render_mipmap', 'OES_standard_derivatives', 'OES_texture_float', 'OES_texture_float_linear', 'OES_texture_half_float', 'OES_texture_half_float_linear', 'OES_vertex_array_object', 'WEBGL_color_buffer_float', 'WEBGL_compressed_texture_s3tc', 'WEBGL_debug_renderer_info', 'WEBGL_debug_shaders', 'WEBGL_depth_texture', 'WEBGL_draw_buffers', 'WEBGL_lose_context' ]; }; } // Also handle WebGL2 context if (window.WebGL2RenderingContext) { const getParameter2 = WebGL2RenderingContext.prototype.getParameter; WebGL2RenderingContext.prototype.getParameter = function(parameter) { if (webglParams.hasOwnProperty(parameter)) { return webglParams[parameter]; } return getParameter2.call(this, parameter); }; const getExtension2 = WebGL2RenderingContext.prototype.getExtension; WebGL2RenderingContext.prototype.getExtension = function(name) { if (name === 'WEBGL_debug_renderer_info') { return { UNMASKED_VENDOR_WEBGL: 37445, UNMASKED_RENDERER_WEBGL: 37446 }; } return getExtension2.call(this, name); }; } }, 'WebGL spoofing'); // WebGL context patching — Proxy wrapper for real contexts + null-context safety safeExecute(() => { const webglSpoofParams = { 37445: GPU_VENDOR, 37446: GPU_RENDERER }; const debugRendererExt = { UNMASKED_VENDOR_WEBGL: 37445, UNMASKED_RENDERER_WEBGL: 37446 }; const originalGetContext = HTMLCanvasElement.prototype.getContext; HTMLCanvasElement.prototype.getContext = function(type, attrs) { const ctx = originalGetContext.call(this, type, attrs); if (type !== 'webgl' && type !== 'experimental-webgl' && type !== 'webgl2') return ctx; const noop = () => {}; // Null context — return mock to prevent crashes if (ctx === null) { const canvasEl = this; // capture the actual canvas element const mock = new Proxy({}, { get(target, prop) { if (prop === 'getShaderPrecisionFormat') return () => ({ rangeMin: 127, rangeMax: 127, precision: 23 }); if (prop === 'getParameter') return (p) => webglSpoofParams[p] || 0; if (prop === 'getSupportedExtensions') return () => []; if (prop === 'getExtension') return (name) => { if (name === 'WEBGL_debug_renderer_info') return { UNMASKED_VENDOR_WEBGL: 37445, UNMASKED_RENDERER_WEBGL: 37446 }; return null; }; if (prop === 'getContextAttributes') return () => ({ alpha: true, antialias: true, depth: true, failIfMajorPerformanceCaveat: false, desynchronized: false, premultipliedAlpha: true, preserveDrawingBuffer: false, powerPreference: 'default', stencil: false, xrCompatible: false }); if (prop === 'isContextLost') return () => false; if (prop === 'canvas') return canvasEl; if (prop === 'drawingBufferWidth') return canvasEl.width || 1920; if (prop === 'drawingBufferHeight') return canvasEl.height || 1080; if (prop === 'drawingBufferColorSpace') return 'srgb'; // Identity — let prototype chain handle constructor/toString/Symbol.toStringTag if (prop === 'constructor') return WebGLRenderingContext; if (prop === Symbol.toStringTag) return 'WebGLRenderingContext'; // Common draw/state methods — return noop to prevent crashes return noop; } }); // Make mock pass instanceof checks if (window.WebGLRenderingContext) { Object.setPrototypeOf(mock, WebGLRenderingContext.prototype); } return mock; } // Real context — wrap in Proxy to intercept getParameter/getExtension // Direct property assignment fails silently on native WebGL objects return new Proxy(ctx, { get(target, prop, receiver) { if (prop === 'getParameter') { return function(param) { if (webglSpoofParams.hasOwnProperty(param)) return webglSpoofParams[param]; return target.getParameter(param); }; } if (prop === 'getExtension') { return function(name) { if (name === 'WEBGL_debug_renderer_info') return debugRendererExt; return target.getExtension(name); }; } const val = Reflect.get(target, prop, receiver); return typeof val === 'function' ? val.bind(target) : val; } }); }; }, 'WebGL context patching'); // Permissions API spoofing // safeExecute(() => { if (navigator.permissions?.query) { const originalQuery = navigator.permissions.query; navigator.permissions.query = function(descriptor) { const permissionName = descriptor.name || descriptor; // Realistic Chrome permission defaults const chromeDefaults = { 'notifications': 'default', 'geolocation': 'prompt', 'camera': 'prompt', 'microphone': 'prompt', 'persistent-storage': 'granted', 'background-sync': 'granted', 'midi': 'prompt', 'push': 'prompt', 'accelerometer': 'granted', 'gyroscope': 'granted', 'magnetometer': 'granted' }; const state = chromeDefaults[permissionName] || 'prompt'; return Promise.resolve({ state, onchange: null }); } } // Block permission prompts from actually appearing if (window.Notification && Notification.requestPermission) { Notification.requestPermission = () => Promise.resolve('default'); } }, 'permissions API spoofing'); // Media Device Spoofing // safeExecute(() => { if (navigator.mediaDevices?.enumerateDevices) { navigator.mediaDevices.enumerateDevices = function() { return Promise.resolve([ { deviceId: 'default', kind: 'audioinput', label: 'Default - Microphone (Realtek Audio)', groupId: 'group1' }, { deviceId: 'default', kind: 'audiooutput', label: 'Default - Speakers (Realtek Audio)', groupId: 'group1' }, { deviceId: 'default', kind: 'videoinput', label: 'HD WebCam (USB Camera)', groupId: 'group2' } ]); }; } }, 'media device spoofing'); // Window dimensions — headless Chrome reports 0 for outer dimensions safeExecute(() => { if (!window.outerWidth || window.outerWidth === 0 || window.outerWidth === window.innerWidth) { Object.defineProperty(window, 'outerWidth', { get: () => window.innerWidth + 16, configurable: true }); } if (!window.outerHeight || window.outerHeight === 0 || window.outerHeight === window.innerHeight) { Object.defineProperty(window, 'outerHeight', { get: () => window.innerHeight + 88, configurable: true }); } if (window.screenX === 0 && window.screenY === 0) { const sX = Math.floor(Math.random() * 200); const sY = Math.floor(Math.random() * 50) + 20; Object.defineProperty(window, 'screenX', { get: () => sX }); Object.defineProperty(window, 'screenY', { get: () => sY }); } }, 'window dimension spoofing'); // navigator.connection — missing or incomplete in headless safeExecute(() => { if (!navigator.connection) { Object.defineProperty(navigator, 'connection', { get: () => ({ effectiveType: '4g', rtt: 50, downlink: 10, saveData: false, type: 'wifi', addEventListener: () => {}, removeEventListener: () => {} }) }); } }, 'connection API spoofing'); // navigator.pdfViewerEnabled — missing in headless, true in real Chrome safeExecute(() => { if (navigator.pdfViewerEnabled === undefined) { Object.defineProperty(navigator, 'pdfViewerEnabled', { get: () => true, configurable: true }); }