From 1c8b0f53ee79b0c3b0d11f0167d835532addbe12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jind=C5=99ich=20B=C3=A4r?= Date: Wed, 24 Jan 2024 15:45:44 +0100 Subject: [PATCH] feat: add the slim mode for disabling performance-heavy evasions (#263) * feat: add the slim mode for disabling performance-heavy evasions * chore: naming conventions --- .../src/fingerprint-generator.ts | 10 +++++ .../src/fingerprint-injector.ts | 7 ++++ packages/fingerprint-injector/src/utils.js | 37 ++++++++++++++++--- 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/packages/fingerprint-generator/src/fingerprint-generator.ts b/packages/fingerprint-generator/src/fingerprint-generator.ts index d43a223b..a407c5c4 100644 --- a/packages/fingerprint-generator/src/fingerprint-generator.ts +++ b/packages/fingerprint-generator/src/fingerprint-generator.ts @@ -63,6 +63,7 @@ export type Fingerprint = { multimediaDevices: string[]; fonts: string[]; mockWebRTC: boolean; + slim?: boolean; } export type BrowserFingerprintWithHeaders = { @@ -82,6 +83,13 @@ export interface FingerprintGeneratorOptions extends HeaderGeneratorOptions { maxHeight?: number; }; mockWebRTC?: boolean; + /** + * Enables the slim mode for the fingerprint injection. + * This disables some performance-heavy evasions, but might decrease benchmark scores. + * + * Try enabling this if you are experiencing performance issues with the fingerprint injection. + */ + slim?: boolean; } /** @@ -99,6 +107,7 @@ export class FingerprintGenerator extends HeaderGenerator { this.fingerprintGlobalOptions = { screen: options.screen, mockWebRTC: options.mockWebRTC, + slim: options.slim, }; this.fingerprintGeneratorNetwork = new BayesianNetwork({ path: `${__dirname}/data_files/fingerprint-network-definition.zip` }); } @@ -181,6 +190,7 @@ export class FingerprintGenerator extends HeaderGenerator { fingerprint: { ...this.transformFingerprint(fingerprint), mockWebRTC: options.mockWebRTC ?? this.fingerprintGlobalOptions.mockWebRTC ?? false, + slim: options.slim ?? this.fingerprintGlobalOptions.slim ?? false, }, headers, }; diff --git a/packages/fingerprint-injector/src/fingerprint-injector.ts b/packages/fingerprint-injector/src/fingerprint-injector.ts index bf0f5824..ebb8acd1 100644 --- a/packages/fingerprint-injector/src/fingerprint-injector.ts +++ b/packages/fingerprint-injector/src/fingerprint-injector.ts @@ -160,6 +160,7 @@ export class FingerprintInjector { audioCodecs, videoCodecs, mockWebRTC, + slim, // @ts-expect-error internal browser code } = fp as EnhancedFingerprint; @@ -203,6 +204,12 @@ export class FingerprintInjector { if (mockWebRTC) blockWebRTC(); + if (slim) { + // @ts-expect-error internal browser code + // eslint-disable-next-line dot-notation + window['slim'] = true; + } + overrideIntlAPI(navigatorProps.language); overrideStatic(); diff --git a/packages/fingerprint-injector/src/utils.js b/packages/fingerprint-injector/src/utils.js index ae1bacc4..42cc676c 100644 --- a/packages/fingerprint-injector/src/utils.js +++ b/packages/fingerprint-injector/src/utils.js @@ -1,7 +1,19 @@ -const isHeadlessChromium = () => /headless/i.test(navigator.userAgent) && navigator.plugins.length === 0; -const isChrome = () => navigator.userAgent.includes("Chrome"); -const isFirefox = () => navigator.userAgent.includes("Firefox"); -const isSafari = () => navigator.userAgent.includes("Safari") && !navigator.userAgent.includes("Chrome"); +const isHeadlessChromium = /headless/i.test(navigator.userAgent) && navigator.plugins.length === 0; +const isChrome = navigator.userAgent.includes("Chrome"); +const isFirefox = navigator.userAgent.includes("Firefox"); +const isSafari = navigator.userAgent.includes("Safari") && !navigator.userAgent.includes("Chrome"); + +let slim = null; +function getSlim() { + if(slim === null) { + slim = window.slim || false; + if(typeof window.slim !== 'undefined') { + delete window.slim; + } + } + + return slim; +} // This file contains utils that are build and included on the window object with some randomized prefix. @@ -102,7 +114,14 @@ function overrideInstancePrototype(instance, overrideObj) { } } +/** + * Updates the .toString method in Function.prototype to return a native string representation of the function. + * @param {*} proxyObj + * @param {*} originalObj + */ function redirectToString(proxyObj, originalObj) { + if(getSlim()) return; + const handler = { setPrototypeOf: (target, newProto) => { try { @@ -131,6 +150,7 @@ function redirectToString(proxyObj, originalObj) { const hasSameProto = Object.getPrototypeOf( Function.prototype.toString, ).isPrototypeOf(ctx.toString); // eslint-disable-line no-prototype-builtins + if (!hasSameProto) { // Pass the call on to the local Function.prototype.toString instead return ctx.toString(); @@ -194,6 +214,11 @@ function redefineProperty(masterObject, propertyName, descriptorOverrides = {}) }); } +/** + * For all the traps in the passed proxy handler, we wrap them in a try/catch and modify the error stack if they throw. + * @param {*} handler A proxy handler object + * @returns A new proxy handler object with error stack modifications + */ function stripProxyFromErrors(handler) { const newHandler = {}; // We wrap each trap in the handler in a try/catch and modify the error stack if they throw @@ -535,7 +560,7 @@ function overrideUserAgentData(userAgentData) { }; function fixWindowChrome(){ - if( isChrome() && !window.chrome ){ + if(isChrome && !window.chrome){ Object.defineProperty(window, 'chrome', { writable: true, enumerable: true, @@ -704,7 +729,7 @@ function fixPluginArray() { function runHeadlessFixes(){ try { - if( isHeadlessChromium() ){ + if( isHeadlessChromium ){ fixWindowChrome(); fixPermissions(); fixIframeContentWindow();