Files
browser-use/stealth-extension/stealth.js
T
2026-03-06 10:09:57 +03:00

629 lines
24 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// =============================================================================
// Stealth Script — инжектируется в MAIN world на каждой странице ДО загрузки
// Подменяет все известные маркеры автоматизации браузера
// =============================================================================
(function() {
'use strict';
// -------------------------------------------------------------------------
// 1. navigator.webdriver = false (подстраховка к Chrome-флагу)
// -------------------------------------------------------------------------
try {
Object.defineProperty(navigator, 'webdriver', {
get: () => false,
configurable: true,
});
} catch (e) {}
// Также удаляем из prototype
try {
const proto = Object.getPrototypeOf(navigator);
if (proto) {
Object.defineProperty(proto, 'webdriver', {
get: () => false,
configurable: true,
});
}
} catch (e) {}
// -------------------------------------------------------------------------
// 2. navigator.plugins — эмулируем стандартные плагины Chrome
// -------------------------------------------------------------------------
try {
const makeMimeType = (type, suffixes, description, plugin) => {
const mt = Object.create(MimeType.prototype);
Object.defineProperties(mt, {
type: { get: () => type },
suffixes: { get: () => suffixes },
description: { get: () => description },
enabledPlugin: { get: () => plugin },
});
return mt;
};
const makePlugin = (name, description, filename, mimeTypes) => {
const p = Object.create(Plugin.prototype);
const mimes = [];
Object.defineProperties(p, {
name: { get: () => name },
description: { get: () => description },
filename: { get: () => filename },
length: { get: () => mimes.length },
});
mimeTypes.forEach((mt, i) => {
const mime = makeMimeType(mt.type, mt.suffixes, mt.description, p);
mimes.push(mime);
Object.defineProperty(p, i, { get: () => mime });
Object.defineProperty(p, mt.type, { get: () => mime });
});
return p;
};
const pluginData = [
{
name: 'Chrome PDF Plugin',
description: 'Portable Document Format',
filename: 'internal-pdf-viewer',
mimeTypes: [
{ type: 'application/x-google-chrome-pdf', suffixes: 'pdf', description: 'Portable Document Format' },
],
},
{
name: 'Chrome PDF Viewer',
description: '',
filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai',
mimeTypes: [
{ type: 'application/pdf', suffixes: 'pdf', description: '' },
],
},
{
name: 'Native Client',
description: '',
filename: 'internal-nacl-plugin',
mimeTypes: [
{ type: 'application/x-nacl', suffixes: '', description: 'Native Client Executable' },
{ type: 'application/x-pnacl', suffixes: '', description: 'Portable Native Client Executable' },
],
},
];
const plugins = pluginData.map(pd =>
makePlugin(pd.name, pd.description, pd.filename, pd.mimeTypes)
);
const pluginArray = Object.create(PluginArray.prototype);
Object.defineProperty(pluginArray, 'length', { get: () => plugins.length });
plugins.forEach((p, i) => {
Object.defineProperty(pluginArray, i, { get: () => p });
Object.defineProperty(pluginArray, p.name, { get: () => p });
});
Object.defineProperty(pluginArray, 'refresh', { value: () => {} });
Object.defineProperty(pluginArray, 'item', {
value: (index) => plugins[index] || null,
});
Object.defineProperty(pluginArray, 'namedItem', {
value: (name) => plugins.find(p => p.name === name) || null,
});
// Итератор
Object.defineProperty(pluginArray, Symbol.iterator, {
value: function* () { for (const p of plugins) yield p; },
});
Object.defineProperty(navigator, 'plugins', {
get: () => pluginArray,
configurable: true,
});
} catch (e) {}
// -------------------------------------------------------------------------
// 3. navigator.mimeTypes — эмулируем
// -------------------------------------------------------------------------
try {
const mimeData = [
{ type: 'application/pdf', suffixes: 'pdf', description: '' },
{ type: 'application/x-google-chrome-pdf', suffixes: 'pdf', description: 'Portable Document Format' },
{ type: 'application/x-nacl', suffixes: '', description: 'Native Client Executable' },
{ type: 'application/x-pnacl', suffixes: '', description: 'Portable Native Client Executable' },
];
const mimeTypeArray = Object.create(MimeTypeArray.prototype);
const mimes = mimeData.map(md => {
const mt = Object.create(MimeType.prototype);
Object.defineProperties(mt, {
type: { get: () => md.type },
suffixes: { get: () => md.suffixes },
description: { get: () => md.description },
enabledPlugin: { get: () => navigator.plugins[0] },
});
return mt;
});
Object.defineProperty(mimeTypeArray, 'length', { get: () => mimes.length });
mimes.forEach((m, i) => {
Object.defineProperty(mimeTypeArray, i, { get: () => m });
Object.defineProperty(mimeTypeArray, m.type, { get: () => m });
});
Object.defineProperty(mimeTypeArray, 'item', {
value: (index) => mimes[index] || null,
});
Object.defineProperty(mimeTypeArray, 'namedItem', {
value: (name) => mimes.find(m => m.type === name) || null,
});
Object.defineProperty(mimeTypeArray, Symbol.iterator, {
value: function* () { for (const m of mimes) yield m; },
});
Object.defineProperty(navigator, 'mimeTypes', {
get: () => mimeTypeArray,
configurable: true,
});
} catch (e) {}
// -------------------------------------------------------------------------
// 4. navigator.languages
// -------------------------------------------------------------------------
try {
Object.defineProperty(navigator, 'languages', {
get: () => ['ru-RU', 'ru', 'en-US', 'en'],
configurable: true,
});
} catch (e) {}
// -------------------------------------------------------------------------
// 5. navigator.pdfViewerEnabled
// -------------------------------------------------------------------------
try {
Object.defineProperty(navigator, 'pdfViewerEnabled', {
get: () => true,
configurable: true,
});
} catch (e) {}
// -------------------------------------------------------------------------
// 6. window.chrome — эмулируем объект chrome
// -------------------------------------------------------------------------
try {
if (!window.chrome) {
window.chrome = {};
}
if (!window.chrome.runtime) {
window.chrome.runtime = {
connect: function() { return { onMessage: { addListener: function() {} }, postMessage: function() {}, onDisconnect: { addListener: function() {} } }; },
sendMessage: function() {},
onMessage: { addListener: function() {}, removeListener: function() {} },
onConnect: { addListener: function() {}, removeListener: function() {} },
id: undefined,
};
}
// chrome.app
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: function() { return null; },
getIsInstalled: function() { return false; },
runningState: function() { return 'cannot_run'; },
};
}
// chrome.csi
if (!window.chrome.csi) {
window.chrome.csi = function() {
return {
startE: Date.now(),
onloadT: Date.now(),
pageT: Math.random() * 1000 + 500,
tran: 15,
};
};
}
// chrome.loadTimes
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 - 0.16,
startLoadTime: Date.now() / 1000 - 0.16,
wasAlternateProtocolAvailable: false,
wasFetchedViaSpdy: true,
wasNpnNegotiated: true,
};
};
}
} catch (e) {}
// -------------------------------------------------------------------------
// 7. Permissions.query — подменяем результат для notifications
// -------------------------------------------------------------------------
try {
const originalQuery = window.Permissions.prototype.query;
window.Permissions.prototype.query = function(parameters) {
if (parameters.name === 'notifications') {
return Promise.resolve({ state: Notification.permission });
}
return originalQuery.call(this, parameters);
};
} catch (e) {}
// -------------------------------------------------------------------------
// 8. navigator.connection — эмулируем NetworkInformation
// -------------------------------------------------------------------------
try {
if (!navigator.connection) {
Object.defineProperty(navigator, 'connection', {
get: () => ({
effectiveType: '4g',
rtt: 50,
downlink: 10,
saveData: false,
onchange: null,
addEventListener: function() {},
removeEventListener: function() {},
dispatchEvent: function() { return true; },
}),
configurable: true,
});
}
} catch (e) {}
// -------------------------------------------------------------------------
// 9. navigator.hardwareConcurrency — реалистичное значение
// -------------------------------------------------------------------------
try {
Object.defineProperty(navigator, 'hardwareConcurrency', {
get: () => 8,
configurable: true,
});
} catch (e) {}
// -------------------------------------------------------------------------
// 10. navigator.deviceMemory — реалистичное значение
// -------------------------------------------------------------------------
try {
Object.defineProperty(navigator, 'deviceMemory', {
get: () => 8,
configurable: true,
});
} catch (e) {}
// -------------------------------------------------------------------------
// 11. navigator.maxTouchPoints — десктоп = 0
// -------------------------------------------------------------------------
try {
Object.defineProperty(navigator, 'maxTouchPoints', {
get: () => 0,
configurable: true,
});
} catch (e) {}
// -------------------------------------------------------------------------
// 12. navigator.platform — Windows
// -------------------------------------------------------------------------
try {
Object.defineProperty(navigator, 'platform', {
get: () => 'Win32',
configurable: true,
});
} catch (e) {}
// -------------------------------------------------------------------------
// 13. navigator.vendor — Google Inc.
// -------------------------------------------------------------------------
try {
Object.defineProperty(navigator, 'vendor', {
get: () => 'Google Inc.',
configurable: true,
});
} catch (e) {}
// -------------------------------------------------------------------------
// 14. navigator.appVersion — совпадает с User-Agent
// -------------------------------------------------------------------------
try {
Object.defineProperty(navigator, 'appVersion', {
get: () => '5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
configurable: true,
});
} catch (e) {}
// -------------------------------------------------------------------------
// 15. navigator.userAgent — подстраховка
// -------------------------------------------------------------------------
try {
Object.defineProperty(navigator, 'userAgent', {
get: () => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
configurable: true,
});
} catch (e) {}
// -------------------------------------------------------------------------
// 16. WebGL — подменяем vendor и renderer
// -------------------------------------------------------------------------
try {
const getParameterOrig = WebGLRenderingContext.prototype.getParameter;
WebGLRenderingContext.prototype.getParameter = function(param) {
// UNMASKED_VENDOR_WEBGL
if (param === 0x9245) return 'Google Inc. (NVIDIA)';
// UNMASKED_RENDERER_WEBGL
if (param === 0x9246) return 'ANGLE (NVIDIA, NVIDIA GeForce GTX 1660 SUPER Direct3D11 vs_5_0 ps_5_0, D3D11)';
return getParameterOrig.call(this, param);
};
const getParameter2Orig = WebGL2RenderingContext.prototype.getParameter;
WebGL2RenderingContext.prototype.getParameter = function(param) {
if (param === 0x9245) return 'Google Inc. (NVIDIA)';
if (param === 0x9246) return 'ANGLE (NVIDIA, NVIDIA GeForce GTX 1660 SUPER Direct3D11 vs_5_0 ps_5_0, D3D11)';
return getParameter2Orig.call(this, param);
};
} catch (e) {}
// -------------------------------------------------------------------------
// 17. Скрываем CDP (Chrome DevTools Protocol) следы
// -------------------------------------------------------------------------
try {
// Удаляем __playwright_target__ если есть
delete window.__playwright_target__;
delete window.__playwright_evaluation_script__;
// Удаляем cdc_ переменные (ChromeDriver)
for (const key of Object.keys(window)) {
if (key.match(/^cdc_/) || key.match(/^__cdc_/) || key.match(/^__selenium_/) || key.match(/^__webdriver_/)) {
try { delete window[key]; } catch (e) {}
}
}
// Удаляем document.$cdc_ переменные
for (const key of Object.keys(document)) {
if (key.match(/^\$cdc_/) || key.match(/^cdc_/)) {
try { delete document[key]; } catch (e) {}
}
}
} catch (e) {}
// -------------------------------------------------------------------------
// 18. Скрываем Headless-маркеры
// -------------------------------------------------------------------------
try {
// window.outerWidth/outerHeight должны быть > 0
if (window.outerWidth === 0) {
Object.defineProperty(window, 'outerWidth', { get: () => 1920 });
}
if (window.outerHeight === 0) {
Object.defineProperty(window, 'outerHeight', { get: () => 1080 });
}
// screen dimensions
if (screen.width === 0 || screen.width < 800) {
Object.defineProperty(screen, 'width', { get: () => 1920 });
Object.defineProperty(screen, 'height', { get: () => 1080 });
Object.defineProperty(screen, 'availWidth', { get: () => 1920 });
Object.defineProperty(screen, 'availHeight', { get: () => 1040 });
Object.defineProperty(screen, 'colorDepth', { get: () => 24 });
Object.defineProperty(screen, 'pixelDepth', { get: () => 24 });
}
} catch (e) {}
// -------------------------------------------------------------------------
// 19. Скрываем iframe-детекцию
// -------------------------------------------------------------------------
try {
// Перехватываем создание iframe и патчим contentWindow
const originalCreateElement = document.createElement.bind(document);
document.createElement = function(tagName, options) {
const el = originalCreateElement(tagName, options);
if (tagName.toLowerCase() === 'iframe') {
const originalContentWindow = Object.getOwnPropertyDescriptor(HTMLIFrameElement.prototype, 'contentWindow');
// Не переопределяем, просто убеждаемся что chrome объект есть в iframe
}
return el;
};
// Сохраняем toString
document.createElement.toString = () => 'function createElement() { [native code] }';
} catch (e) {}
// -------------------------------------------------------------------------
// 20. Защита toString() — все подменённые функции должны выглядеть нативно
// -------------------------------------------------------------------------
try {
const nativeToString = Function.prototype.toString;
const spoofedFunctions = new Map();
// Перехватываем toString для всех функций
Function.prototype.toString = function() {
if (spoofedFunctions.has(this)) {
return spoofedFunctions.get(this);
}
return nativeToString.call(this);
};
// Регистрируем подменённые функции
spoofedFunctions.set(Function.prototype.toString, 'function toString() { [native code] }');
if (window.Permissions && window.Permissions.prototype.query) {
spoofedFunctions.set(window.Permissions.prototype.query, 'function query() { [native code] }');
}
} catch (e) {}
// -------------------------------------------------------------------------
// 21. Notification.permission — подстраховка
// -------------------------------------------------------------------------
try {
if (Notification.permission === 'denied') {
Object.defineProperty(Notification, 'permission', {
get: () => 'default',
configurable: true,
});
}
} catch (e) {}
// -------------------------------------------------------------------------
// 22. Скрываем автоматизацию в User-Agent Client Hints
// -------------------------------------------------------------------------
try {
if (navigator.userAgentData) {
Object.defineProperty(navigator, 'userAgentData', {
get: () => ({
brands: [
{ brand: 'Google Chrome', version: '131' },
{ brand: 'Chromium', version: '131' },
{ brand: 'Not_A Brand', version: '24' },
],
mobile: false,
platform: 'Windows',
getHighEntropyValues: function(hints) {
return Promise.resolve({
architecture: 'x86',
bitness: '64',
brands: [
{ brand: 'Google Chrome', version: '131' },
{ brand: 'Chromium', version: '131' },
{ brand: 'Not_A Brand', version: '24' },
],
fullVersionList: [
{ brand: 'Google Chrome', version: '131.0.6778.86' },
{ brand: 'Chromium', version: '131.0.6778.86' },
{ brand: 'Not_A Brand', version: '24.0.0.0' },
],
mobile: false,
model: '',
platform: 'Windows',
platformVersion: '15.0.0',
uaFullVersion: '131.0.6778.86',
wow64: false,
});
},
toJSON: function() {
return {
brands: this.brands,
mobile: this.mobile,
platform: this.platform,
};
},
}),
configurable: true,
});
}
} catch (e) {}
// -------------------------------------------------------------------------
// 23. Скрываем Brave/automation-специфичные API
// -------------------------------------------------------------------------
try {
// Убеждаемся что нет window.domAutomation и window.domAutomationController
delete window.domAutomation;
delete window.domAutomationController;
delete window._phantom;
delete window.__nightmare;
delete window._selenium;
delete window.callPhantom;
delete window.callSelenium;
delete window._Recaptcha;
} catch (e) {}
// -------------------------------------------------------------------------
// 24. Эмуляция Battery API
// -------------------------------------------------------------------------
try {
if (navigator.getBattery) {
navigator.getBattery = function() {
return Promise.resolve({
charging: true,
chargingTime: 0,
dischargingTime: Infinity,
level: 1.0,
onchargingchange: null,
onchargingtimechange: null,
ondischargingtimechange: null,
onlevelchange: null,
addEventListener: function() {},
removeEventListener: function() {},
dispatchEvent: function() { return true; },
});
};
}
} catch (e) {}
// -------------------------------------------------------------------------
// 25. Скрываем Automation flags в navigator
// -------------------------------------------------------------------------
try {
// navigator.brave не должен существовать (мы не Brave)
if (navigator.brave) {
delete navigator.brave;
}
} catch (e) {}
// -------------------------------------------------------------------------
// 26. AudioContext — автоматический resume для обхода autoplay policy
// -------------------------------------------------------------------------
try {
const OriginalAudioContext = window.AudioContext || window.webkitAudioContext;
if (OriginalAudioContext) {
const originalResume = OriginalAudioContext.prototype.resume;
const originalConstructor = OriginalAudioContext;
// Патчим конструктор чтобы автоматически resume
window.AudioContext = function(...args) {
const ctx = new originalConstructor(...args);
// Автоматически resume если suspended
if (ctx.state === 'suspended') {
try { ctx.resume(); } catch(e) {}
}
return ctx;
};
window.AudioContext.prototype = originalConstructor.prototype;
if (window.webkitAudioContext) {
window.webkitAudioContext = window.AudioContext;
}
}
} catch (e) {}
// -------------------------------------------------------------------------
// 27. Скрываем факт использования расширений от content scripts detection
// -------------------------------------------------------------------------
try {
// Некоторые антифроды проверяют chrome.runtime.id — если он есть, значит расширение
// Мы уже определили chrome.runtime выше, но убедимся что id = undefined
if (window.chrome && window.chrome.runtime) {
Object.defineProperty(window.chrome.runtime, 'id', {
get: () => undefined,
configurable: true,
});
}
} catch (e) {}
// -------------------------------------------------------------------------
// 28. Скрываем sourceURL в стеке вызовов (anti-extension detection)
// -------------------------------------------------------------------------
try {
const originalError = Error;
const originalPrepareStackTrace = Error.prepareStackTrace;
Error.prepareStackTrace = function(error, stack) {
// Фильтруем фреймы, содержащие chrome-extension://
const filteredStack = stack.filter(frame => {
const fileName = frame.getFileName();
return !fileName || !fileName.startsWith('chrome-extension://');
});
if (originalPrepareStackTrace) {
return originalPrepareStackTrace(error, filteredStack);
}
return filteredStack.map(frame => ` at ${frame}`).join('\n');
};
} catch (e) {}
// -------------------------------------------------------------------------
// Готово — все патчи применены
// -------------------------------------------------------------------------
})();