package extractor // stealthChromiumArgs are launch arguments that reduce automation detection for Chromium-based browsers. var stealthChromiumArgs = []string{ "--disable-blink-features=AutomationControlled", } // stealthCommonScripts are JavaScript snippets injected before page scripts on all browser engines. var stealthCommonScripts = []string{ // Override navigator.webdriver to return undefined (the real-browser value). `Object.defineProperty(navigator, 'webdriver', {get: () => undefined})`, // Fix outerWidth/outerHeight which are 0 in headless mode. `if (window.outerWidth === 0) { Object.defineProperty(window, 'outerWidth', { get: () => window.innerWidth }); Object.defineProperty(window, 'outerHeight', { get: () => window.innerHeight }); }`, // Override navigator.permissions.query to return "denied" for notifications. `(function() { if (navigator.permissions && navigator.permissions.query) { const origQuery = navigator.permissions.query.bind(navigator.permissions); navigator.permissions.query = function(desc) { if (desc && desc.name === 'notifications') { return Promise.resolve({ state: 'denied', onchange: null }); } return origQuery(desc); }; } })()`, // Stub Notification constructor if missing (headless may lack it). `(function() { if (typeof Notification === 'undefined') { window.Notification = function() {}; Notification.permission = 'denied'; Notification.requestPermission = function() { return Promise.resolve('denied'); }; } })()`, } // stealthChromiumScripts are JavaScript snippets specific to Chromium-based browsers. var stealthChromiumScripts = []string{ // Populate navigator.plugins with realistic Chromium entries so plugins.length > 0. `Object.defineProperty(navigator, 'plugins', { get: () => { const arr = [ { name: 'PDF Viewer', filename: 'internal-pdf-viewer', description: 'Portable Document Format' }, { name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: '' }, { name: 'Chromium PDF Viewer', filename: 'internal-pdf-viewer', description: '' }, ]; arr.item = (i) => arr[i] || null; arr.namedItem = (n) => arr.find(p => p.name === n) || null; arr.refresh = () => {}; return arr; }, })`, // Populate navigator.mimeTypes to match the fake Chromium plugins above. `Object.defineProperty(navigator, 'mimeTypes', { get: () => { const arr = [ { type: 'application/pdf', suffixes: 'pdf', description: 'Portable Document Format' }, ]; arr.item = (i) => arr[i] || null; arr.namedItem = (n) => arr.find(m => m.type === n) || null; return arr; }, })`, // Provide window.chrome runtime stub (Chromium-only signal). `if (!window.chrome) { window.chrome = { runtime: {} }; }`, // Add chrome.app, chrome.csi, and chrome.loadTimes stubs missing in headless. `(function() { if (!window.chrome) window.chrome = {}; 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' } }; } if (!window.chrome.csi) { window.chrome.csi = function() { return { startE: Date.now(), onloadT: Date.now(), pageT: 0, tran: 15 }; }; } if (!window.chrome.loadTimes) { window.chrome.loadTimes = function() { return { commitLoadTime: Date.now() / 1000, connectionInfo: 'h2', finishDocumentLoadTime: Date.now() / 1000, finishLoadTime: Date.now() / 1000, firstPaintAfterLoadTime: 0, firstPaintTime: Date.now() / 1000, navigationType: 'Other', npnNegotiatedProtocol: 'h2', requestTime: Date.now() / 1000, startLoadTime: Date.now() / 1000, wasAlternateProtocolAvailable: false, wasFetchedViaSpdy: true, wasNpnNegotiated: true }; }; } })()`, // Spoof WebGL renderer to hide SwiftShader (headless GPU) fingerprint with Chromium ANGLE strings. `(function() { const getParam = WebGLRenderingContext.prototype.getParameter; WebGLRenderingContext.prototype.getParameter = function(param) { if (param === 37445) return 'Google Inc. (Intel)'; if (param === 37446) return 'ANGLE (Intel, Intel(R) UHD Graphics 630, OpenGL 4.5)'; return getParam.call(this, param); }; if (typeof WebGL2RenderingContext !== 'undefined') { const getParam2 = WebGL2RenderingContext.prototype.getParameter; WebGL2RenderingContext.prototype.getParameter = function(param) { if (param === 37445) return 'Google Inc. (Intel)'; if (param === 37446) return 'ANGLE (Intel, Intel(R) UHD Graphics 630, OpenGL 4.5)'; return getParam2.call(this, param); }; } })()`, // Stub navigator.connection (Network Information API) if missing (Chrome-only API). `(function() { if (!navigator.connection) { Object.defineProperty(navigator, 'connection', { get: function() { return { effectiveType: '4g', rtt: 50, downlink: 10, saveData: false, onchange: null }; }, }); } })()`, // Remove CDP artifacts (window.cdc_* globals injected by Chrome DevTools Protocol). `(function() { for (var key in window) { if (key.match(/^cdc_/)) { delete window[key]; } } })()`, // Strip "HeadlessChrome" from navigator.userAgent if present. `(function() { var ua = navigator.userAgent; if (ua.indexOf('HeadlessChrome') !== -1) { Object.defineProperty(navigator, 'userAgent', { get: function() { return ua.replace('HeadlessChrome', 'Chrome'); }, }); } })()`, } // stealthFirefoxScripts are JavaScript snippets specific to Firefox. var stealthFirefoxScripts = []string{ // Harden navigator.webdriver for Firefox: ensure Object.getOwnPropertyDescriptor also returns undefined. `(function() { const proto = Object.getPrototypeOf(navigator); const origGetOwnPropDesc = Object.getOwnPropertyDescriptor; Object.getOwnPropertyDescriptor = function(obj, prop) { if ((obj === navigator || obj === proto) && prop === 'webdriver') { return undefined; } return origGetOwnPropDesc.call(this, obj, prop); }; })()`, // Spoof WebGL renderer with Firefox-appropriate Mesa/Intel strings. `(function() { const getParam = WebGLRenderingContext.prototype.getParameter; WebGLRenderingContext.prototype.getParameter = function(param) { if (param === 37445) return 'Intel Open Source Technology Center'; if (param === 37446) return 'Mesa DRI Intel(R) UHD Graphics 630'; return getParam.call(this, param); }; if (typeof WebGL2RenderingContext !== 'undefined') { const getParam2 = WebGL2RenderingContext.prototype.getParameter; WebGL2RenderingContext.prototype.getParameter = function(param) { if (param === 37445) return 'Intel Open Source Technology Center'; if (param === 37446) return 'Mesa DRI Intel(R) UHD Graphics 630'; return getParam2.call(this, param); }; } })()`, // Spoof mozInnerScreenX/mozInnerScreenY which are 0 in headless Firefox. `(function() { if (window.mozInnerScreenX === 0) { Object.defineProperty(window, 'mozInnerScreenX', { get: () => 8 }); } if (window.mozInnerScreenY === 0) { Object.defineProperty(window, 'mozInnerScreenY', { get: () => 51 }); } })()`, // Normalize navigator.hardwareConcurrency (Firefox headless sometimes reports 2). `(function() { if (navigator.hardwareConcurrency <= 2) { Object.defineProperty(navigator, 'hardwareConcurrency', { get: () => 4 }); } })()`, // Override navigator.plugins with Firefox-appropriate PDF.js entry. `Object.defineProperty(navigator, 'plugins', { get: () => { const arr = [ { name: 'PDF.js', filename: 'internal-pdf-viewer', description: 'Portable Document Format' }, ]; arr.item = (i) => arr[i] || null; arr.namedItem = (n) => arr.find(p => p.name === n) || null; arr.refresh = () => {}; return arr; }, })`, }