@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
JavaScript
// === 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
});
}