Files
stealthii/stealthii/stealth_js.py
T
retoorandClaude Sonnet 5 a3bb7639d2 Initial version of stealthii
An aiohttp/httpx-shaped async HTTP client backed by a single, persistent,
stealth-patched Chromium instance via Playwright. get/post/put/patch/
delete/head/download use context.request (real Chrome TLS/HTTP2
fingerprint, no tab needed); render()/screenshot()/page() escalate to
full JS-executing page rendering for targets that need a JS challenge
solved. Supports named sessions for cookie persistence/warm-up, and
`async with stealth as page` to claim a tab directly, task-safe for
concurrent use on one shared instance. Context/browser recycling bounds
cookie and on-disk cache growth in a long-lived process.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0116i7dYLNZTXNR7Lqf439bV
2026-09-09 13:49:07 +02:00

120 lines
5.1 KiB
Python

# retoor <retoor@molodetz.nl>
"""Browser-side JS patches injected into every page/context before any page
script runs. Removes the automation tells that a stock headless Chromium
otherwise exposes (navigator.webdriver, missing window.chrome, empty
PluginArray, SwiftShader WebGL renderer, permissions-query mismatch,
iframe.contentWindow leaking the absence of window.chrome).
"""
STEALTH_INIT_JS = r"""
(() => {
const patchNativeToString = (fn, name) => {
const src = `function ${name}() { [native code] }`;
const proxied = new Proxy(fn, {
apply(target, thisArg, args) { return Reflect.apply(target, thisArg, args); },
});
Object.defineProperty(proxied, 'toString', {
value: () => src, configurable: true, enumerable: false, writable: false,
});
return proxied;
};
const define = (obj, prop, getter) => {
try {
Object.defineProperty(obj, prop, { get: patchNativeToString(getter, 'get ' + prop), configurable: true });
} catch (e) {}
};
define(Navigator.prototype, 'webdriver', () => undefined);
try {
if (!window.chrome || !window.chrome.runtime) {
window.chrome = {
runtime: {
connect: () => {}, sendMessage: () => {}, onMessage: { addListener: () => {} },
id: undefined,
},
loadTimes: function () {}, csi: function () {}, 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' },
},
};
}
} catch (e) {}
try {
const originalQuery = window.navigator.permissions && window.navigator.permissions.query;
if (originalQuery) {
window.navigator.permissions.query = patchNativeToString((parameters) => (
parameters && parameters.name === 'notifications'
? Promise.resolve({ state: Notification.permission, onchange: null })
: originalQuery(parameters)
), 'query');
}
} catch (e) {}
try {
const fakeMimeType = (type, description, suffixes) => ({ type, description, suffixes, enabledPlugin: null });
const fakePlugin = (name, description, filename, mimes) => {
const p = { name, description, filename, length: mimes.length };
mimes.forEach((m, i) => { p[i] = m; m.enabledPlugin = p; });
p.item = (i) => p[i] || null;
p.namedItem = (n) => mimes.find((m) => m.type === n) || null;
return p;
};
const pdfMime = fakeMimeType('application/pdf', 'Portable Document Format', 'pdf');
const plugins = [
fakePlugin('PDF Viewer', 'Portable Document Format', 'internal-pdf-viewer', [pdfMime]),
fakePlugin('Chrome PDF Viewer', 'Portable Document Format', 'internal-pdf-viewer', [pdfMime]),
fakePlugin('Chromium PDF Viewer', 'Portable Document Format', 'internal-pdf-viewer', [pdfMime]),
fakePlugin('Microsoft Edge PDF Viewer', 'Portable Document Format', 'internal-pdf-viewer', [pdfMime]),
fakePlugin('WebKit built-in PDF', 'Portable Document Format', 'internal-pdf-viewer', [pdfMime]),
];
const pluginArray = plugins;
pluginArray.item = (i) => pluginArray[i] || null;
pluginArray.namedItem = (n) => pluginArray.find((p) => p.name === n) || null;
pluginArray.refresh = () => {};
pluginArray.forEach((p) => {
try { Object.setPrototypeOf(p, Plugin.prototype); } catch (e) {}
Object.defineProperty(p, Symbol.toStringTag, { value: 'Plugin', configurable: true });
});
Object.defineProperty(pluginArray, Symbol.toStringTag, { value: 'PluginArray', configurable: true });
try { Object.setPrototypeOf(pluginArray, PluginArray.prototype); } catch (e) {}
define(Navigator.prototype, 'plugins', () => pluginArray);
define(Navigator.prototype, 'mimeTypes', () => {
const arr = [pdfMime];
Object.defineProperty(arr, Symbol.toStringTag, { value: 'MimeTypeArray', configurable: true });
try { Object.setPrototypeOf(arr, MimeTypeArray.prototype); } catch (e) {}
return arr;
});
} catch (e) {}
define(Navigator.prototype, 'deviceMemory', () => 8);
define(Navigator.prototype, 'hardwareConcurrency', () => 8);
try {
const spoofVendor = (ctxProto) => {
const original = ctxProto.getParameter;
ctxProto.getParameter = patchNativeToString(function (parameter) {
if (parameter === 37445) return 'Google Inc. (NVIDIA)';
if (parameter === 37446) return 'ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 Direct3D11 vs_5_0 ps_5_0, D3D11)';
return original.apply(this, arguments);
}, 'getParameter');
};
spoofVendor(WebGLRenderingContext.prototype);
if (window.WebGL2RenderingContext) spoofVendor(WebGL2RenderingContext.prototype);
} catch (e) {}
try {
const contentWindowDesc = Object.getOwnPropertyDescriptor(HTMLIFrameElement.prototype, 'contentWindow');
Object.defineProperty(HTMLIFrameElement.prototype, 'contentWindow', {
get: patchNativeToString(function () {
const win = contentWindowDesc.get.call(this);
try { if (win && !win.chrome) win.chrome = window.chrome; } catch (e) {}
return win;
}, 'get contentWindow'),
});
} catch (e) {}
})();
"""