@fanboynz/network-scanner 1.0.64 → 1.0.65
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/fingerprint.js +524 -1825
- package/nwss.js +2 -2
- package/package.json +1 -1
package/lib/fingerprint.js
CHANGED
|
@@ -1,198 +1,106 @@
|
|
|
1
1
|
// === Enhanced Fingerprint Protection Module - Puppeteer 23.x Compatible ===
|
|
2
2
|
// This module handles advanced browser fingerprint spoofing, user agent changes,
|
|
3
3
|
// and comprehensive bot detection evasion techniques.
|
|
4
|
+
//const { applyErrorSuppression } = require('./error-suppression');
|
|
4
5
|
|
|
5
6
|
// Default values for fingerprint spoofing if not set to 'random'
|
|
6
7
|
const DEFAULT_PLATFORM = 'Win32';
|
|
7
8
|
const DEFAULT_TIMEZONE = 'America/New_York';
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
*/
|
|
12
|
-
const BUILT_IN_PROPERTIES = [
|
|
10
|
+
// Built-in properties that should not be modified
|
|
11
|
+
const BUILT_IN_PROPERTIES = new Set([
|
|
13
12
|
'href', 'origin', 'protocol', 'host', 'hostname', 'port', 'pathname', 'search', 'hash',
|
|
14
|
-
'constructor', 'prototype', '__proto__', 'toString', 'valueOf',
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
'constructor', 'prototype', '__proto__', 'toString', 'valueOf', 'assign', 'reload', 'replace'
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
// User agent collections with latest versions
|
|
17
|
+
const USER_AGENT_COLLECTIONS = {
|
|
18
|
+
chrome: [
|
|
19
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
|
|
20
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
|
|
21
|
+
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
|
|
22
|
+
],
|
|
23
|
+
firefox: [
|
|
24
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:133.0) Gecko/20100101 Firefox/133.0",
|
|
25
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:133.0) Gecko/20100101 Firefox/133.0",
|
|
26
|
+
"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:133.0) Gecko/20100101 Firefox/133.0"
|
|
27
|
+
],
|
|
28
|
+
safari: [
|
|
29
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.2 Safari/605.1.15",
|
|
30
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.6 Safari/605.1.15"
|
|
31
|
+
]
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Timezone configuration with offsets
|
|
35
|
+
const TIMEZONE_CONFIG = {
|
|
36
|
+
'America/New_York': { offset: 300, abbr: 'EST' },
|
|
37
|
+
'America/Los_Angeles': { offset: 480, abbr: 'PST' },
|
|
38
|
+
'Europe/London': { offset: 0, abbr: 'GMT' },
|
|
39
|
+
'America/Chicago': { offset: 360, abbr: 'CST' }
|
|
40
|
+
};
|
|
17
41
|
|
|
18
42
|
/**
|
|
19
|
-
*
|
|
43
|
+
* Safely defines a property with comprehensive error handling
|
|
20
44
|
*/
|
|
21
|
-
function
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
return ['href', 'origin', 'protocol', 'host', 'hostname', 'port', 'pathname', 'search', 'hash'].includes(property);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return BUILT_IN_PROPERTIES.includes(property);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Creates a safe property descriptor for Puppeteer 23.x
|
|
33
|
-
*/
|
|
34
|
-
function createSafeDescriptor(descriptor, existingDescriptor) {
|
|
35
|
-
const safeDescriptor = { ...descriptor };
|
|
36
|
-
|
|
37
|
-
// Always ensure configurable is true unless specifically set to false
|
|
38
|
-
if (safeDescriptor.configurable !== false) {
|
|
39
|
-
safeDescriptor.configurable = true;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Preserve existing enumerable state if not specified
|
|
43
|
-
if (safeDescriptor.enumerable === undefined && existingDescriptor) {
|
|
44
|
-
safeDescriptor.enumerable = existingDescriptor.enumerable;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Ensure writable is properly set for data descriptors
|
|
48
|
-
if ('value' in safeDescriptor && safeDescriptor.writable === undefined) {
|
|
49
|
-
safeDescriptor.writable = true;
|
|
45
|
+
function safeDefineProperty(target, property, descriptor, options = {}) {
|
|
46
|
+
if (BUILT_IN_PROPERTIES.has(property)) {
|
|
47
|
+
if (options.debug) console.log(`[fingerprint] Skipping built-in property: ${property}`);
|
|
48
|
+
return false;
|
|
50
49
|
}
|
|
51
|
-
|
|
52
|
-
return safeDescriptor;
|
|
53
|
-
}
|
|
54
50
|
|
|
55
|
-
/**
|
|
56
|
-
* Safely defines or redefines a property with error handling for Puppeteer 23.x
|
|
57
|
-
* @param {Object} target - Target object
|
|
58
|
-
* @param {string} property - Property name
|
|
59
|
-
* @param {Object} descriptor - Property descriptor
|
|
60
|
-
* @param {boolean} forceDebug - Debug logging flag
|
|
61
|
-
*/
|
|
62
|
-
function safeDefineProperty(target, property, descriptor, forceDebug = false) {
|
|
63
51
|
try {
|
|
64
|
-
|
|
65
|
-
if (
|
|
52
|
+
const existing = Object.getOwnPropertyDescriptor(target, property);
|
|
53
|
+
if (existing?.configurable === false) {
|
|
54
|
+
if (options.debug) console.log(`[fingerprint] Cannot modify non-configurable: ${property}`);
|
|
66
55
|
return false;
|
|
67
56
|
}
|
|
68
|
-
const existingDescriptor = Object.getOwnPropertyDescriptor(target, property);
|
|
69
57
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (forceDebug) {
|
|
75
|
-
console.log(`[fingerprint] Skipping built-in property: ${property}`);
|
|
76
|
-
}
|
|
77
|
-
return false;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Check if property is truly non-configurable
|
|
81
|
-
if (existingDescriptor.configurable === false) {
|
|
82
|
-
if (forceDebug) {
|
|
83
|
-
console.log(`[fingerprint] Cannot redefine non-configurable property: ${property}`);
|
|
84
|
-
}
|
|
85
|
-
return false;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Enhanced descriptor validation
|
|
90
|
-
const safeDescriptor = createSafeDescriptor(descriptor, existingDescriptor);
|
|
91
|
-
|
|
92
|
-
Object.defineProperty(target, property, safeDescriptor);
|
|
58
|
+
Object.defineProperty(target, property, {
|
|
59
|
+
...descriptor,
|
|
60
|
+
configurable: true
|
|
61
|
+
});
|
|
93
62
|
return true;
|
|
94
|
-
} catch (
|
|
95
|
-
if (
|
|
96
|
-
console.log(`[fingerprint] Property definition failed for ${property}: ${defineErr.message}`);
|
|
97
|
-
}
|
|
63
|
+
} catch (err) {
|
|
64
|
+
if (options.debug) console.log(`[fingerprint] Failed to define ${property}: ${err.message}`);
|
|
98
65
|
return false;
|
|
99
66
|
}
|
|
100
67
|
}
|
|
101
68
|
|
|
102
69
|
/**
|
|
103
|
-
*
|
|
104
|
-
* Consolidated from canSafelyModifyProperty to avoid duplication
|
|
70
|
+
* Safely executes spoofing operations with error handling
|
|
105
71
|
*/
|
|
106
|
-
function
|
|
107
|
-
try {
|
|
108
|
-
// COMPREHENSIVE DEBUG: Log every canModifyProperty check
|
|
109
|
-
if (forceDebug) {
|
|
110
|
-
console.log(`[fingerprint] canModifyProperty called for: ${property} on target:`, target.constructor?.name || 'unknown');
|
|
111
|
-
if (property === 'href') {
|
|
112
|
-
console.log(`[fingerprint] CRITICAL: Checking if href can be modified!`);
|
|
113
|
-
console.log(`[fingerprint] Target details:`, {
|
|
114
|
-
isWindow: target === window,
|
|
115
|
-
isLocation: target === window.location,
|
|
116
|
-
constructorName: target.constructor?.name
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Check if it's a built-in property that shouldn't be modified
|
|
122
|
-
if (isBuiltInProperty(target, property)) {
|
|
123
|
-
if (forceDebug) {
|
|
124
|
-
console.log(`[fingerprint] Skipping built-in property: ${property}`);
|
|
125
|
-
}
|
|
126
|
-
return false;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const descriptor = Object.getOwnPropertyDescriptor(target, property);
|
|
130
|
-
if (!descriptor) return true; // Property doesn't exist, can be added
|
|
131
|
-
|
|
132
|
-
return descriptor.configurable !== false;
|
|
133
|
-
} catch (checkErr) {
|
|
134
|
-
if (forceDebug) {
|
|
135
|
-
console.log(`[fingerprint] Property check failed for ${property}: ${checkErr.message}`);
|
|
136
|
-
}
|
|
137
|
-
return false; // If we can't check, assume we can't modify
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Safely executes fingerprint spoofing code with comprehensive error handling
|
|
143
|
-
* @param {Function} spoofFunction - Function to execute
|
|
144
|
-
* @param {string} description - Description of the spoofing operation
|
|
145
|
-
* @param {boolean} forceDebug - Debug logging flag
|
|
146
|
-
*/
|
|
147
|
-
function safeExecuteSpoofing(spoofFunction, description, forceDebug = false) {
|
|
72
|
+
function safeSpoofingExecution(spoofFunction, description, options = {}) {
|
|
148
73
|
try {
|
|
149
74
|
spoofFunction();
|
|
150
75
|
return true;
|
|
151
|
-
} catch (
|
|
152
|
-
|
|
153
|
-
const isCriticalError = spoofErr.message.includes('Cannot redefine property') ||
|
|
154
|
-
spoofErr.message.includes('non-configurable') ||
|
|
155
|
-
spoofErr.message.includes('Invalid property descriptor');
|
|
156
|
-
|
|
157
|
-
if (forceDebug) {
|
|
158
|
-
const errorLevel = isCriticalError ? 'CRITICAL' : 'WARNING';
|
|
159
|
-
console.log(`[fingerprint] ${errorLevel} - ${description} failed: ${spoofErr.message}`);
|
|
160
|
-
}
|
|
161
|
-
// Continue execution - don't let spoofing failures break the scan
|
|
76
|
+
} catch (err) {
|
|
77
|
+
if (options.debug) console.log(`[fingerprint] ${description} failed: ${err.message}`);
|
|
162
78
|
return false;
|
|
163
79
|
}
|
|
164
80
|
}
|
|
165
81
|
|
|
166
82
|
/**
|
|
167
83
|
* Generates realistic screen resolutions based on common monitor sizes
|
|
168
|
-
* @returns {object} Screen resolution object with width and height
|
|
169
84
|
*/
|
|
170
85
|
function getRealisticScreenResolution() {
|
|
171
86
|
const commonResolutions = [
|
|
172
|
-
{ width: 1920, height: 1080 },
|
|
173
|
-
{ width: 1366, height: 768 },
|
|
174
|
-
{ width: 1440, height: 900 },
|
|
175
|
-
{ width: 1536, height: 864 },
|
|
176
|
-
{ width: 1600, height: 900 },
|
|
177
|
-
{ width: 2560, height: 1440 },
|
|
178
|
-
{ width: 1280, height: 720 },
|
|
179
|
-
{ width: 3440, height: 1440 }
|
|
87
|
+
{ width: 1920, height: 1080 },
|
|
88
|
+
{ width: 1366, height: 768 },
|
|
89
|
+
{ width: 1440, height: 900 },
|
|
90
|
+
{ width: 1536, height: 864 },
|
|
91
|
+
{ width: 1600, height: 900 },
|
|
92
|
+
{ width: 2560, height: 1440 },
|
|
93
|
+
{ width: 1280, height: 720 },
|
|
94
|
+
{ width: 3440, height: 1440 }
|
|
180
95
|
];
|
|
181
|
-
|
|
182
96
|
return commonResolutions[Math.floor(Math.random() * commonResolutions.length)];
|
|
183
97
|
}
|
|
184
98
|
|
|
185
99
|
/**
|
|
186
|
-
* Generates
|
|
187
|
-
* This is used to spoof various navigator and screen properties to make
|
|
188
|
-
* the headless browser instance appear more like a regular user's browser
|
|
189
|
-
* and bypass fingerprint-based bot detection.
|
|
190
|
-
*
|
|
191
|
-
* @returns {object} An object containing the spoofed fingerprint properties
|
|
100
|
+
* Generates randomized but realistic browser fingerprint values
|
|
192
101
|
*/
|
|
193
102
|
function getRandomFingerprint() {
|
|
194
103
|
const resolution = getRealisticScreenResolution();
|
|
195
|
-
|
|
196
104
|
return {
|
|
197
105
|
deviceMemory: [4, 8, 16, 32][Math.floor(Math.random() * 4)],
|
|
198
106
|
hardwareConcurrency: [2, 4, 6, 8, 12, 16][Math.floor(Math.random() * 6)],
|
|
@@ -200,7 +108,7 @@ function getRandomFingerprint() {
|
|
|
200
108
|
width: resolution.width,
|
|
201
109
|
height: resolution.height,
|
|
202
110
|
availWidth: resolution.width,
|
|
203
|
-
availHeight: resolution.height - 40,
|
|
111
|
+
availHeight: resolution.height - 40,
|
|
204
112
|
colorDepth: 24,
|
|
205
113
|
pixelDepth: 24
|
|
206
114
|
},
|
|
@@ -213,1178 +121,428 @@ function getRandomFingerprint() {
|
|
|
213
121
|
}
|
|
214
122
|
|
|
215
123
|
/**
|
|
216
|
-
*
|
|
217
|
-
* Compatible with Puppeteer 23.x
|
|
218
|
-
* @param {import('puppeteer').Page} page - The Puppeteer page instance
|
|
219
|
-
* @param {object} siteConfig - The site configuration object
|
|
220
|
-
* @param {boolean} forceDebug - Whether debug logging is enabled
|
|
221
|
-
* @param {string} currentUrl - The current URL being processed (for logging)
|
|
222
|
-
* @returns {Promise<void>}
|
|
124
|
+
* Creates mock Chrome runtime objects
|
|
223
125
|
*/
|
|
224
|
-
|
|
225
|
-
|
|
126
|
+
function createMockChromeRuntime() {
|
|
127
|
+
return {
|
|
128
|
+
onConnect: { addListener: () => {}, removeListener: () => {} },
|
|
129
|
+
onMessage: { addListener: () => {}, removeListener: () => {} },
|
|
130
|
+
sendMessage: () => {},
|
|
131
|
+
connect: () => ({
|
|
132
|
+
onMessage: { addListener: () => {}, removeListener: () => {} },
|
|
133
|
+
postMessage: () => {},
|
|
134
|
+
disconnect: () => {}
|
|
135
|
+
}),
|
|
136
|
+
getManifest: () => ({ name: "Chrome", version: "131.0.0.0" }),
|
|
137
|
+
getURL: (path) => `chrome-extension://invalid/${path}`,
|
|
138
|
+
id: undefined
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Generates realistic Chrome loadTimes data
|
|
144
|
+
*/
|
|
145
|
+
function generateRealisticLoadTimes() {
|
|
146
|
+
const now = performance.now();
|
|
147
|
+
return {
|
|
148
|
+
commitLoadTime: now - Math.random() * 1000,
|
|
149
|
+
connectionInfo: 'http/1.1',
|
|
150
|
+
finishDocumentLoadTime: now - Math.random() * 500,
|
|
151
|
+
finishLoadTime: now - Math.random() * 100,
|
|
152
|
+
firstPaintAfterLoadTime: now - Math.random() * 50,
|
|
153
|
+
firstPaintTime: now - Math.random() * 200,
|
|
154
|
+
navigationType: 'Navigation',
|
|
155
|
+
npnNegotiatedProtocol: 'unknown',
|
|
156
|
+
requestTime: now - Math.random() * 2000,
|
|
157
|
+
startLoadTime: now - Math.random() * 1500,
|
|
158
|
+
wasAlternateProtocolAvailable: false,
|
|
159
|
+
wasFetchedViaSpdy: false,
|
|
160
|
+
wasNpnNegotiated: false
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Generates Chrome CSI data
|
|
166
|
+
*/
|
|
167
|
+
function generateCSIData() {
|
|
168
|
+
return {
|
|
169
|
+
onloadT: Date.now(),
|
|
170
|
+
pageT: Math.random() * 1000,
|
|
171
|
+
startE: Date.now() - Math.random() * 2000,
|
|
172
|
+
tran: Math.floor(Math.random() * 20)
|
|
173
|
+
};
|
|
174
|
+
}
|
|
226
175
|
|
|
227
|
-
|
|
176
|
+
/**
|
|
177
|
+
* Creates mock fingerprinting objects
|
|
178
|
+
*/
|
|
179
|
+
function createFingerprintMocks() {
|
|
180
|
+
const mockResult = {
|
|
181
|
+
visitorId: 'mock_visitor_' + Math.random().toString(36).substr(2, 9),
|
|
182
|
+
confidence: { score: 0.99 },
|
|
183
|
+
components: {
|
|
184
|
+
screen: { value: { width: 1920, height: 1080 } },
|
|
185
|
+
timezone: { value: DEFAULT_TIMEZONE },
|
|
186
|
+
language: { value: 'en-US' }
|
|
187
|
+
}
|
|
188
|
+
};
|
|
228
189
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
190
|
+
return {
|
|
191
|
+
fp: {
|
|
192
|
+
getResult: (callback) => callback ? setTimeout(() => callback(mockResult), 0) : mockResult,
|
|
193
|
+
get: (callback) => Promise.resolve(mockResult),
|
|
194
|
+
load: () => Promise.resolve(window.fp),
|
|
195
|
+
components: { screen: { value: { width: 1920, height: 1080 } } },
|
|
196
|
+
x64hash128: () => 'mock_hash',
|
|
197
|
+
tz: DEFAULT_TIMEZONE,
|
|
198
|
+
timezone: DEFAULT_TIMEZONE
|
|
199
|
+
},
|
|
200
|
+
FingerprintJS: {
|
|
201
|
+
load: () => Promise.resolve({
|
|
202
|
+
get: () => Promise.resolve(mockResult)
|
|
203
|
+
})
|
|
204
|
+
},
|
|
205
|
+
ClientJS: function() {
|
|
206
|
+
this.getFingerprint = () => 'mock_fingerprint_' + Math.random().toString(36).substr(2, 9);
|
|
207
|
+
this.getBrowser = () => 'Chrome';
|
|
208
|
+
this.getOS = () => 'Windows';
|
|
209
|
+
this.fp = {};
|
|
210
|
+
}
|
|
246
211
|
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Applies timezone spoofing
|
|
216
|
+
*/
|
|
217
|
+
function applyTimezoneSpoofing(timezone, options = {}) {
|
|
218
|
+
const tzConfig = TIMEZONE_CONFIG[timezone] || TIMEZONE_CONFIG[DEFAULT_TIMEZONE];
|
|
247
219
|
|
|
248
|
-
|
|
220
|
+
// Spoof Intl.DateTimeFormat
|
|
221
|
+
if (window.Intl?.DateTimeFormat) {
|
|
222
|
+
const OriginalDateTimeFormat = window.Intl.DateTimeFormat;
|
|
223
|
+
window.Intl.DateTimeFormat = function(...args) {
|
|
224
|
+
const instance = new OriginalDateTimeFormat(...args);
|
|
225
|
+
const originalResolvedOptions = instance.resolvedOptions;
|
|
226
|
+
|
|
227
|
+
instance.resolvedOptions = function() {
|
|
228
|
+
const opts = originalResolvedOptions.call(this);
|
|
229
|
+
opts.timeZone = timezone;
|
|
230
|
+
return opts;
|
|
231
|
+
};
|
|
232
|
+
return instance;
|
|
233
|
+
};
|
|
234
|
+
Object.setPrototypeOf(window.Intl.DateTimeFormat, OriginalDateTimeFormat);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Spoof Date.getTimezoneOffset
|
|
238
|
+
const originalGetTimezoneOffset = Date.prototype.getTimezoneOffset;
|
|
239
|
+
Date.prototype.getTimezoneOffset = function() {
|
|
240
|
+
return tzConfig.offset;
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Enhanced user agent spoofing with stealth protection
|
|
248
|
+
*/
|
|
249
|
+
async function applyUserAgentSpoofing(page, siteConfig, forceDebug, currentUrl) {
|
|
250
|
+
if (!siteConfig.userAgent) return;
|
|
251
|
+
|
|
252
|
+
if (forceDebug) console.log(`[debug] User agent spoofing: ${siteConfig.userAgent}`);
|
|
253
|
+
|
|
254
|
+
const selectedUserAgents = USER_AGENT_COLLECTIONS[siteConfig.userAgent.toLowerCase()];
|
|
249
255
|
const ua = selectedUserAgents ? selectedUserAgents[Math.floor(Math.random() * selectedUserAgents.length)] : null;
|
|
250
256
|
|
|
251
257
|
if (ua) {
|
|
252
258
|
await page.setUserAgent(ua);
|
|
253
259
|
|
|
254
|
-
|
|
255
|
-
if (forceDebug) console.log(`[debug] Applying enhanced stealth protection for ${currentUrl}`);
|
|
260
|
+
if (forceDebug) console.log(`[debug] Applying stealth protection for ${currentUrl}`);
|
|
256
261
|
|
|
257
262
|
try {
|
|
258
263
|
await page.evaluateOnNewDocument((userAgent, debugEnabled) => {
|
|
259
264
|
|
|
260
|
-
//
|
|
261
|
-
function
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
return true;
|
|
265
|
-
} catch (spoofErr) {
|
|
266
|
-
if (debugEnabled) {
|
|
267
|
-
console.log(`[fingerprint] ${description} failed: ${spoofErr.message}`);
|
|
268
|
-
}
|
|
269
|
-
return false;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// Helper function for safe property definition (local scope)
|
|
274
|
-
function safeDefineProperty(target, property, descriptor) {
|
|
275
|
-
try {
|
|
276
|
-
// COMPREHENSIVE DEBUG: Log every property definition attempt
|
|
277
|
-
if (debugEnabled) {
|
|
278
|
-
console.log(`[fingerprint] safeDefineProperty called for: ${property} on target:`, target.constructor?.name || 'unknown');
|
|
279
|
-
if (property === 'href') {
|
|
280
|
-
console.log(`[fingerprint] CRITICAL: Attempting to define href property!`);
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// Enhanced built-in property protection using global constant
|
|
285
|
-
if (property === 'href' || ['href', 'origin', 'protocol', 'host', 'hostname', 'port', 'pathname', 'search', 'hash', 'constructor', 'prototype', '__proto__', 'toString', 'valueOf', 'assign', 'reload', 'replace'].includes(property)) {
|
|
286
|
-
if (debugEnabled) {
|
|
287
|
-
console.log(`[fingerprint] BLOCKED: Built-in property ${property} blocked in safeDefineProperty`);
|
|
288
|
-
}
|
|
289
|
-
return false;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
const existingDescriptor = Object.getOwnPropertyDescriptor(target, property);
|
|
293
|
-
|
|
294
|
-
if (existingDescriptor && existingDescriptor.configurable === false) {
|
|
295
|
-
if (debugEnabled) {
|
|
296
|
-
console.log(`[fingerprint] Cannot redefine non-configurable property: ${property}`);
|
|
297
|
-
}
|
|
298
|
-
return false;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
const safeDescriptor = {
|
|
302
|
-
...descriptor,
|
|
303
|
-
configurable: true // Always use configurable: true for Puppeteer 23.x compatibility
|
|
304
|
-
};
|
|
305
|
-
|
|
306
|
-
Object.defineProperty(target, property, safeDescriptor);
|
|
307
|
-
return true;
|
|
308
|
-
} catch (defineErr) {
|
|
309
|
-
if (debugEnabled) {
|
|
310
|
-
console.log(`[fingerprint] Property definition failed for ${property}: ${defineErr.message}`);
|
|
311
|
-
}
|
|
312
|
-
return false;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// Validate properties before attempting to modify them (local scope)
|
|
317
|
-
function canModifyProperty(target, property) {
|
|
318
|
-
try {
|
|
319
|
-
// COMPREHENSIVE DEBUG: Log every canModifyProperty check
|
|
320
|
-
if (debugEnabled) {
|
|
321
|
-
console.log(`[fingerprint] canModifyProperty called for: ${property} on target:`, target.constructor?.name || 'unknown');
|
|
322
|
-
if (property === 'href') {
|
|
323
|
-
console.log(`[fingerprint] CRITICAL: Checking if href can be modified!`);
|
|
324
|
-
console.log(`[fingerprint] Target details:`, {
|
|
325
|
-
isWindow: target === window,
|
|
326
|
-
isLocation: target === window.location,
|
|
327
|
-
constructorName: target.constructor?.name
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// Enhanced built-in property check with location object handling
|
|
333
|
-
const builtInProperties = ['href', 'origin', 'protocol', 'host', 'hostname', 'port', 'pathname', 'search', 'hash', 'constructor', 'prototype', '__proto__', 'toString', 'valueOf', 'assign', 'reload', 'replace'];
|
|
334
|
-
|
|
335
|
-
// Special handling for Location object properties
|
|
336
|
-
if (target === window.location || (target.constructor && target.constructor.name === 'Location')) {
|
|
337
|
-
const locationProperties = ['href', 'origin', 'protocol', 'host', 'hostname', 'port', 'pathname', 'search', 'hash'];
|
|
338
|
-
if (locationProperties.includes(property)) {
|
|
339
|
-
if (debugEnabled) {
|
|
340
|
-
console.log(`[fingerprint] Skipping location property: ${property}`);
|
|
341
|
-
}
|
|
342
|
-
return false;
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
if (builtInProperties.includes(property)) {
|
|
347
|
-
if (debugEnabled) {
|
|
348
|
-
console.log(`[fingerprint] Skipping built-in property: ${property}`);
|
|
349
|
-
}
|
|
350
|
-
return false;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
const descriptor = Object.getOwnPropertyDescriptor(target, property);
|
|
354
|
-
if (!descriptor) return true; // Property doesn't exist, can be added
|
|
355
|
-
|
|
356
|
-
return descriptor.configurable !== false;
|
|
357
|
-
} catch (checkErr) {
|
|
358
|
-
return false; // If we can't check, assume we can't modify
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
// GLOBAL HREF PROTECTION: Override Object.defineProperty to block ALL href modifications
|
|
363
|
-
const originalDefineProperty = Object.defineProperty;
|
|
364
|
-
Object.defineProperty = function(target, property, descriptor) {
|
|
365
|
-
// Block ALL attempts to redefine href anywhere
|
|
366
|
-
if (property === 'href') {
|
|
367
|
-
if (debugEnabled) {
|
|
368
|
-
console.log(`[fingerprint] GLOBAL BLOCK: Prevented ${property} redefinition on:`, target.constructor?.name || 'unknown');
|
|
369
|
-
console.trace('[fingerprint] Call stack for blocked href:');
|
|
370
|
-
}
|
|
371
|
-
// Return false to indicate failure, but don't throw
|
|
372
|
-
return false;
|
|
373
|
-
}
|
|
265
|
+
// Apply inline error suppression first
|
|
266
|
+
(function() {
|
|
267
|
+
const originalConsoleError = console.error;
|
|
268
|
+
const originalWindowError = window.onerror;
|
|
374
269
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
270
|
+
function shouldSuppressFingerprintError(message) {
|
|
271
|
+
const patterns = [
|
|
272
|
+
/\.closest is not a function/i,
|
|
273
|
+
/\.querySelector is not a function/i,
|
|
274
|
+
/\.addEventListener is not a function/i,
|
|
275
|
+
/Cannot read propert(y|ies) of null \\(reading 'fp'\\)/i,
|
|
276
|
+
/Cannot read propert(y|ies) of undefined \\(reading 'fp'\\)/i,
|
|
277
|
+
/Cannot redefine property: href/i,
|
|
278
|
+
/Cannot redefine property: __webdriver_script_func/i,
|
|
279
|
+
/Cannot redefine property: webdriver/i,
|
|
280
|
+
/Cannot read propert(y|ies) of undefined \\(reading 'toLowerCase'\\)/i,
|
|
281
|
+
/\\.toLowerCase is not a function/i,
|
|
282
|
+
/fp is not defined/i,
|
|
283
|
+
/fingerprint is not defined/i,
|
|
284
|
+
/FingerprintJS is not defined/i,
|
|
285
|
+
/\\$ is not defined/i,
|
|
286
|
+
/jQuery is not defined/i,
|
|
287
|
+
/_ is not defined/i,
|
|
288
|
+
/Failed to load resource.*server responded with a status of [45]\\d{2}/i,
|
|
289
|
+
/Failed to fetch/i,
|
|
290
|
+
/(webdriver|callPhantom|_phantom|__nightmare|_selenium) is not defined/i,
|
|
291
|
+
/Failed to execute 'observe' on 'IntersectionObserver'.*parameter 1 is not of type 'Element'/i,
|
|
292
|
+
/tz check/i,
|
|
293
|
+
/new window\\.Error.*<anonymous>/i,
|
|
294
|
+
/Failed to load resource.*server responded with a status of 40[34]/i,
|
|
295
|
+
/Blocked script execution in 'about:blank'.*sandboxed.*allow-scripts/i
|
|
296
|
+
];
|
|
297
|
+
return patterns.some(pattern => pattern.test(String(message || '')));
|
|
382
298
|
}
|
|
383
299
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
console.log(`[fingerprint] Original defineProperty failed for ${property}:`, err.message);
|
|
300
|
+
console.error = function(...args) {
|
|
301
|
+
const message = args.join(' ');
|
|
302
|
+
if (shouldSuppressFingerprintError(message)) {
|
|
303
|
+
if (debugEnabled) console.log("[fingerprint] Suppressed error:", message);
|
|
304
|
+
return;
|
|
390
305
|
}
|
|
391
|
-
return
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
if (!target) {
|
|
399
|
-
if (debugEnabled) {
|
|
400
|
-
console.log(`[fingerprint] NULL TARGET: Prevented property access on null target for property: ${property}`);
|
|
306
|
+
return originalConsoleError.apply(this, arguments);
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
window.onerror = function(message, source, lineno, colno, error) {
|
|
310
|
+
if (shouldSuppressFingerprintError(message)) {
|
|
311
|
+
if (debugEnabled) console.log("[fingerprint] Suppressed window error:", message);
|
|
312
|
+
return true;
|
|
401
313
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
return originalGetOwnPropertyDescriptor.apply(this, arguments);
|
|
405
|
-
};
|
|
406
|
-
|
|
407
|
-
// Add safe property access wrapper
|
|
408
|
-
window.safePropertyAccess = function(obj, property, defaultValue = null) {
|
|
409
|
-
try {
|
|
410
|
-
return (obj && obj[property] !== undefined) ? obj[property] : defaultValue;
|
|
411
|
-
} catch (err) {
|
|
412
|
-
return defaultValue;
|
|
413
|
-
}
|
|
414
|
-
};
|
|
415
|
-
|
|
416
|
-
// Enhanced null object protection for property access
|
|
417
|
-
const originalHasOwnProperty = Object.prototype.hasOwnProperty;
|
|
418
|
-
Object.prototype.hasOwnProperty = function(property) {
|
|
419
|
-
if (this === null || this === undefined) {
|
|
420
|
-
if (debugEnabled && property === 'fp') {
|
|
421
|
-
console.log(`[fingerprint] NULL OBJECT: Prevented hasOwnProperty('${property}') on null/undefined`);
|
|
314
|
+
if (originalWindowError) {
|
|
315
|
+
return originalWindowError.apply(this, arguments);
|
|
422
316
|
}
|
|
423
317
|
return false;
|
|
424
|
-
}
|
|
425
|
-
return originalHasOwnProperty.call(this, property);
|
|
426
|
-
};
|
|
427
|
-
|
|
428
|
-
// Enhanced undefined object protection for method access
|
|
429
|
-
const originalPropertyAccess = Object.getOwnPropertyDescriptor;
|
|
430
|
-
|
|
431
|
-
// Global protection against undefined method access
|
|
432
|
-
const createSafeMethodProxy = (methodName) => {
|
|
433
|
-
return function(...args) {
|
|
434
|
-
if (debugEnabled) {
|
|
435
|
-
console.log(`[fingerprint] Safe ${methodName} proxy called with args:`, args);
|
|
436
|
-
}
|
|
437
|
-
// For alert specifically, show the message safely
|
|
438
|
-
if (methodName === 'alert' && args.length > 0) {
|
|
439
|
-
try {
|
|
440
|
-
window.alert(args[0]);
|
|
441
|
-
} catch (alertErr) {
|
|
442
|
-
if (debugEnabled) {
|
|
443
|
-
console.log(`[fingerprint] Safe alert failed, logging instead: ${args[0]}`);
|
|
444
|
-
}
|
|
445
|
-
console.log(`[Alert] ${args[0]}`);
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
// For other methods, just return a safe empty function
|
|
449
|
-
return undefined;
|
|
450
318
|
};
|
|
451
|
-
};
|
|
319
|
+
})();
|
|
452
320
|
|
|
453
|
-
// Create safe
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
window.safePrompt = createSafeMethodProxy('prompt');
|
|
457
|
-
|
|
458
|
-
// Global error handler for fingerprinting access errors
|
|
459
|
-
const originalErrorHandler = window.onerror;
|
|
460
|
-
window.onerror = function(message, source, lineno, colno, error) {
|
|
461
|
-
// Handle fingerprinting-related null access errors
|
|
462
|
-
if (typeof message === 'string' && (
|
|
463
|
-
message.includes("Cannot read properties of null (reading 'fp')") ||
|
|
464
|
-
message.includes("Cannot read property 'fp' of null") ||
|
|
465
|
-
message.includes("Cannot read properties of undefined (reading 'alert')") ||
|
|
466
|
-
message.includes("Cannot read property 'alert' of undefined") ||
|
|
467
|
-
message.includes("Cannot read properties of undefined (reading 'confirm')") ||
|
|
468
|
-
message.includes("Cannot read properties of undefined (reading 'prompt')") ||
|
|
469
|
-
message.includes("fp is not defined") ||
|
|
470
|
-
message.includes("alert is not defined") ||
|
|
471
|
-
message.includes("is not a function") ||
|
|
472
|
-
message.includes("Cannot read properties of undefined (reading") ||
|
|
473
|
-
message.includes("Cannot read properties of null (reading") ||
|
|
474
|
-
message.includes("fingerprint") && message.includes("null")
|
|
475
|
-
)) {
|
|
476
|
-
if (debugEnabled) {
|
|
477
|
-
console.log(`[fingerprint] Suppressed fingerprinting null access error: ${message}`);
|
|
478
|
-
}
|
|
479
|
-
return true; // Prevent error from showing in console
|
|
480
|
-
}
|
|
321
|
+
// Create safe property definition helper
|
|
322
|
+
function safeDefinePropertyLocal(target, property, descriptor) {
|
|
323
|
+
const builtInProps = new Set(['href', 'origin', 'protocol', 'host', 'hostname', 'port', 'pathname', 'search', 'hash', 'constructor', 'prototype', '__proto__', 'toString', 'valueOf', 'assign', 'reload', 'replace']);
|
|
481
324
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
return
|
|
325
|
+
if (builtInProps.has(property)) {
|
|
326
|
+
if (debugEnabled) console.log(`[fingerprint] Skipping built-in property: ${property}`);
|
|
327
|
+
return false;
|
|
485
328
|
}
|
|
486
|
-
return false;
|
|
487
|
-
};
|
|
488
|
-
|
|
489
|
-
// CRITICAL: Simplified CDP Detection Prevention
|
|
490
|
-
// Prevents detection via Chrome DevTools Protocol Error.stack analysis
|
|
491
|
-
safeExecuteSpoofing(() => {
|
|
492
|
-
// Store original Error constructor
|
|
493
|
-
const OriginalError = window.Error;
|
|
494
|
-
|
|
495
|
-
// Override Error constructor to prevent CDP detection
|
|
496
|
-
window.Error = function(...args) {
|
|
497
|
-
const error = new OriginalError(...args);
|
|
498
|
-
|
|
499
|
-
// Get original stack descriptor to preserve behavior
|
|
500
|
-
const originalStackDescriptor = Object.getOwnPropertyDescriptor(error, 'stack') ||
|
|
501
|
-
Object.getOwnPropertyDescriptor(OriginalError.prototype, 'stack');
|
|
502
|
-
|
|
503
|
-
// Override stack property to prevent CDP detection via Error.stack getter
|
|
504
|
-
Object.defineProperty(error, 'stack', {
|
|
505
|
-
get: function() {
|
|
506
|
-
// This is the critical part - prevent CDP detection flag from being set
|
|
507
|
-
// Anti-bot systems set a flag when Error.stack getter is accessed
|
|
508
|
-
|
|
509
|
-
let stack;
|
|
510
|
-
if (originalStackDescriptor && originalStackDescriptor.get) {
|
|
511
|
-
try {
|
|
512
|
-
stack = originalStackDescriptor.get.call(this);
|
|
513
|
-
} catch (stackErr) {
|
|
514
|
-
stack = 'Error\n at unknown location';
|
|
515
|
-
}
|
|
516
|
-
} else if (originalStackDescriptor && originalStackDescriptor.value) {
|
|
517
|
-
stack = originalStackDescriptor.value;
|
|
518
|
-
} else {
|
|
519
|
-
// Fallback stack trace
|
|
520
|
-
stack = `${this.name || 'Error'}: ${this.message || ''}\n at unknown location`;
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
// Clean automation traces from stack if present
|
|
524
|
-
if (typeof stack === 'string') {
|
|
525
|
-
stack = stack
|
|
526
|
-
.replace(/.*puppeteer.*\n?/gi, '')
|
|
527
|
-
.replace(/.*chrome-devtools.*\n?/gi, '')
|
|
528
|
-
.replace(/.*webdriver.*\n?/gi, '')
|
|
529
|
-
.replace(/.*automation.*\n?/gi, '')
|
|
530
|
-
.replace(/\n\s*\n/g, '\n')
|
|
531
|
-
.trim();
|
|
532
|
-
|
|
533
|
-
// Ensure we always have a valid stack
|
|
534
|
-
if (!stack) {
|
|
535
|
-
stack = `${this.name || 'Error'}: ${this.message || ''}\n at unknown location`;
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
return stack;
|
|
540
|
-
},
|
|
541
|
-
set: function(value) {
|
|
542
|
-
// Allow stack to be set normally
|
|
543
|
-
if (originalStackDescriptor && originalStackDescriptor.set) {
|
|
544
|
-
originalStackDescriptor.set.call(this, value);
|
|
545
|
-
} else {
|
|
546
|
-
// Create internal property if no setter exists
|
|
547
|
-
Object.defineProperty(this, '_internalStack', {
|
|
548
|
-
value: value,
|
|
549
|
-
writable: true,
|
|
550
|
-
configurable: true
|
|
551
|
-
});
|
|
552
|
-
}
|
|
553
|
-
},
|
|
554
|
-
configurable: true,
|
|
555
|
-
enumerable: false
|
|
556
|
-
});
|
|
557
|
-
|
|
558
|
-
return error;
|
|
559
|
-
};
|
|
560
|
-
|
|
561
|
-
// Preserve Error prototype and constructor properties
|
|
562
|
-
window.Error.prototype = OriginalError.prototype;
|
|
563
|
-
Object.setPrototypeOf(window.Error, OriginalError);
|
|
564
|
-
|
|
565
|
-
// Copy essential static properties
|
|
566
|
-
['captureStackTrace', 'stackTraceLimit', 'prepareStackTrace'].forEach(prop => {
|
|
567
|
-
if (OriginalError[prop]) {
|
|
568
|
-
try {
|
|
569
|
-
window.Error[prop] = OriginalError[prop];
|
|
570
|
-
} catch (propErr) {
|
|
571
|
-
// Ignore if property can't be copied
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
});
|
|
575
|
-
|
|
576
|
-
// Enhanced Error.captureStackTrace protection
|
|
577
|
-
if (OriginalError.captureStackTrace) {
|
|
578
|
-
window.Error.captureStackTrace = function(targetObject, constructorOpt) {
|
|
579
|
-
try {
|
|
580
|
-
const result = OriginalError.captureStackTrace.call(this, targetObject, constructorOpt);
|
|
581
|
-
|
|
582
|
-
// Clean captured stack trace
|
|
583
|
-
if (targetObject && targetObject.stack && typeof targetObject.stack === 'string') {
|
|
584
|
-
targetObject.stack = targetObject.stack
|
|
585
|
-
.replace(/.*puppeteer.*\n?/gi, '')
|
|
586
|
-
.replace(/.*chrome-devtools.*\n?/gi, '')
|
|
587
|
-
.replace(/.*webdriver.*\n?/gi, '')
|
|
588
|
-
.replace(/.*automation.*\n?/gi, '')
|
|
589
|
-
.replace(/\n\s*\n/g, '\n')
|
|
590
|
-
.trim();
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
return result;
|
|
594
|
-
} catch (captureErr) {
|
|
595
|
-
if (debugEnabled) {
|
|
596
|
-
console.log('[fingerprint] captureStackTrace error handled:', captureErr.message);
|
|
597
|
-
}
|
|
598
|
-
return undefined;
|
|
599
|
-
}
|
|
600
|
-
};
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
// Prevent global CDP detection flag
|
|
604
|
-
try {
|
|
605
|
-
Object.defineProperty(window, 'cdpDetected', {
|
|
606
|
-
get: () => false,
|
|
607
|
-
set: () => false,
|
|
608
|
-
configurable: false,
|
|
609
|
-
enumerable: false
|
|
610
|
-
});
|
|
611
|
-
} catch (cdpPropErr) {
|
|
612
|
-
// Ignore if property already exists
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
// Additional protection: prevent common CDP detection patterns
|
|
616
|
-
const cdpDetectionStrings = ['cdpDetected', 'chromeDevtools', 'runtimeEvaluate'];
|
|
617
|
-
cdpDetectionStrings.forEach(str => {
|
|
618
|
-
try {
|
|
619
|
-
if (!window[str]) {
|
|
620
|
-
Object.defineProperty(window, str, {
|
|
621
|
-
get: () => false,
|
|
622
|
-
set: () => false,
|
|
623
|
-
configurable: false,
|
|
624
|
-
enumerable: false
|
|
625
|
-
});
|
|
626
|
-
}
|
|
627
|
-
} catch (strErr) {
|
|
628
|
-
// Ignore property definition errors
|
|
629
|
-
}
|
|
630
|
-
});
|
|
631
|
-
|
|
632
|
-
if (debugEnabled) {
|
|
633
|
-
console.log('[fingerprint] Simplified CDP detection prevention installed - Error constructor protected');
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
}, 'Simplified CDP Detection Prevention');
|
|
637
|
-
|
|
638
|
-
// Test function for CDP detection (only in debug mode)
|
|
639
|
-
if (debugEnabled) {
|
|
640
|
-
try {
|
|
641
|
-
let testCdpDetected = false;
|
|
642
|
-
const testError = new Error('test');
|
|
643
|
-
|
|
644
|
-
// Simulate anti-bot CDP detection attempt
|
|
645
|
-
Object.defineProperty(testError, 'stack', {
|
|
646
|
-
get() {
|
|
647
|
-
testCdpDetected = true;
|
|
648
|
-
return 'test stack trace';
|
|
649
|
-
}
|
|
650
|
-
});
|
|
651
|
-
|
|
652
|
-
// Access stack - should NOT trigger detection with our patch
|
|
653
|
-
const testStack = testError.stack;
|
|
654
|
-
|
|
655
|
-
console.log(`[fingerprint] CDP protection test: ${testCdpDetected ? 'FAILED - Detection triggered!' : 'PASSED - Protection working'}`);
|
|
656
|
-
} catch (testErr) {
|
|
657
|
-
console.log('[fingerprint] CDP test error:', testErr.message);
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
329
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
visitorId: 'mock_visitor_id_' + Math.random().toString(36).substring(7),
|
|
667
|
-
confidence: { score: 0.99 },
|
|
668
|
-
components: {
|
|
669
|
-
screen: { value: { width: 1920, height: 1080 } },
|
|
670
|
-
timezone: { value: 'America/New_York' },
|
|
671
|
-
language: { value: 'en-US' }
|
|
672
|
-
}
|
|
673
|
-
};
|
|
674
|
-
if (typeof callback === 'function') {
|
|
675
|
-
try {
|
|
676
|
-
setTimeout(() => callback(result), 0);
|
|
677
|
-
} catch (callbackErr) {
|
|
678
|
-
if (debugEnabled) console.log(`[fingerprint] FP callback error: ${callbackErr.message}`);
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
return result;
|
|
682
|
-
},
|
|
683
|
-
get: (callback) => {
|
|
684
|
-
const result = {
|
|
685
|
-
visitorId: 'mock_visitor_id_' + Math.random().toString(36).substring(7),
|
|
686
|
-
confidence: { score: 0.99 },
|
|
687
|
-
components: {}
|
|
688
|
-
};
|
|
689
|
-
if (typeof callback === 'function') {
|
|
690
|
-
try {
|
|
691
|
-
setTimeout(() => callback(result), 0);
|
|
692
|
-
} catch (callbackErr) {
|
|
693
|
-
if (debugEnabled) console.log(`[fingerprint] FP.get callback error: ${callbackErr.message}`);
|
|
694
|
-
}
|
|
330
|
+
try {
|
|
331
|
+
const existing = Object.getOwnPropertyDescriptor(target, property);
|
|
332
|
+
if (existing?.configurable === false) {
|
|
333
|
+
if (debugEnabled) console.log(`[fingerprint] Cannot modify non-configurable: ${property}`);
|
|
334
|
+
return false;
|
|
695
335
|
}
|
|
696
|
-
return Promise.resolve(result);
|
|
697
|
-
},
|
|
698
|
-
load: () => Promise.resolve(window.fp),
|
|
699
|
-
components: {
|
|
700
|
-
screen: { value: { width: 1920, height: 1080 } }
|
|
701
|
-
},
|
|
702
|
-
x64hash128: () => 'mock_hash',
|
|
703
|
-
tz: 'America/New_York', // Mock timezone
|
|
704
|
-
timezone: 'America/New_York'
|
|
705
|
-
};
|
|
706
|
-
|
|
707
|
-
// Enhanced timezone protection - create comprehensive timezone objects
|
|
708
|
-
window.timezone = window.timezone || 'America/New_York';
|
|
709
|
-
// Create comprehensive FingerprintJS mock objects
|
|
710
|
-
window.FingerprintJS = window.FingerprintJS || {
|
|
711
|
-
load: (options) => Promise.resolve({
|
|
712
|
-
get: (getOptions) => Promise.resolve({
|
|
713
|
-
visitorId: 'mock_visitor_id_' + Math.random().toString(36).substring(7),
|
|
714
|
-
confidence: { score: 0.99 },
|
|
715
|
-
components: {}
|
|
716
|
-
})
|
|
717
|
-
})
|
|
718
|
-
};
|
|
719
|
-
|
|
720
|
-
// Mock other common fingerprinting libraries that might access .fp
|
|
721
|
-
window.ClientJS = window.ClientJS || function() {
|
|
722
|
-
this.getFingerprint = () => 'mock_fingerprint_' + Math.random().toString(36).substring(7);
|
|
723
|
-
this.getBrowser = () => 'Chrome';
|
|
724
|
-
this.getOS = () => 'Windows';
|
|
725
|
-
this.fp = {}; // Prevent null access
|
|
726
|
-
};
|
|
727
|
-
|
|
728
|
-
// Create safe proxy wrapper for fingerprinting objects
|
|
729
|
-
const createFingerprintProxy = (targetName) => {
|
|
730
|
-
if (!window[targetName]) {
|
|
731
|
-
window[targetName] = new Proxy({}, {
|
|
732
|
-
get: (target, prop) => {
|
|
733
|
-
if (debugEnabled && prop === 'fp') {
|
|
734
|
-
console.log(`[fingerprint] Safe proxy accessed: ${targetName}.fp`);
|
|
735
|
-
}
|
|
736
|
-
if (prop === 'fp') {
|
|
737
|
-
return {}; // Return safe empty object for .fp access
|
|
738
|
-
}
|
|
739
|
-
// Handle common method access on undefined objects
|
|
740
|
-
if (prop === 'alert') {
|
|
741
|
-
return window.safeAlert;
|
|
742
|
-
}
|
|
743
|
-
if (prop === 'confirm') {
|
|
744
|
-
return window.safeConfirm;
|
|
745
|
-
}
|
|
746
|
-
if (prop === 'prompt') {
|
|
747
|
-
return window.safePrompt;
|
|
748
|
-
}
|
|
749
|
-
// Return safe empty function for other method-like properties
|
|
750
|
-
if (typeof target[prop] === 'undefined' && prop.endsWith && (prop.endsWith('alert') || prop.endsWith('confirm') || prop.endsWith('prompt'))) {
|
|
751
|
-
return createSafeMethodProxy(prop);
|
|
752
|
-
}
|
|
753
|
-
return target[prop] || undefined;
|
|
754
|
-
}
|
|
755
|
-
});
|
|
756
|
-
}
|
|
757
|
-
};
|
|
758
|
-
|
|
759
|
-
// Create safe proxies for common fingerprinting object names
|
|
760
|
-
['fpjs', 'fingerprint', 'deviceFingerprint', 'browserFingerprint', 'fpCollector'].forEach(createFingerprintProxy);
|
|
761
336
|
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
if (!window[objName]) {
|
|
766
|
-
window[objName] = new Proxy({}, {
|
|
767
|
-
get: (target, prop) => {
|
|
768
|
-
if (debugEnabled && ['alert', 'confirm', 'prompt'].includes(prop)) {
|
|
769
|
-
console.log(`[fingerprint] Safe proxy method accessed: ${objName}.${prop}`);
|
|
770
|
-
}
|
|
771
|
-
if (prop === 'alert') {
|
|
772
|
-
return window.safeAlert;
|
|
773
|
-
}
|
|
774
|
-
if (prop === 'confirm') {
|
|
775
|
-
return window.safeConfirm;
|
|
776
|
-
}
|
|
777
|
-
if (prop === 'prompt') {
|
|
778
|
-
return window.safePrompt;
|
|
779
|
-
}
|
|
780
|
-
// Return safe function for any other method access
|
|
781
|
-
return () => undefined;
|
|
782
|
-
}
|
|
337
|
+
Object.defineProperty(target, property, {
|
|
338
|
+
...descriptor,
|
|
339
|
+
configurable: true
|
|
783
340
|
});
|
|
784
|
-
|
|
785
|
-
});
|
|
786
|
-
|
|
787
|
-
// Global protection for undefined objects accessing dialog methods
|
|
788
|
-
const originalAlert = window.alert;
|
|
789
|
-
const originalConfirm = window.confirm;
|
|
790
|
-
const originalPrompt = window.prompt;
|
|
791
|
-
|
|
792
|
-
// Ensure these methods are always available and safe
|
|
793
|
-
window.alert = window.alert || function(message) {
|
|
794
|
-
console.log(`[Alert] ${message}`);
|
|
795
|
-
};
|
|
796
|
-
window.confirm = window.confirm || function(message) {
|
|
797
|
-
console.log(`[Confirm] ${message}`);
|
|
798
|
-
return false;
|
|
799
|
-
};
|
|
800
|
-
window.prompt = window.prompt || function(message, defaultValue) {
|
|
801
|
-
console.log(`[Prompt] ${message}`);
|
|
802
|
-
return defaultValue || null;
|
|
803
|
-
};
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
window.timeZone = window.timeZone || 'America/New_York';
|
|
807
|
-
|
|
808
|
-
// Comprehensive Date prototype protection
|
|
809
|
-
const originalDateToString = Date.prototype.toString;
|
|
810
|
-
Date.prototype.toString = function() {
|
|
811
|
-
try {
|
|
812
|
-
return originalDateToString.call(this);
|
|
341
|
+
return true;
|
|
813
342
|
} catch (err) {
|
|
814
|
-
|
|
343
|
+
if (debugEnabled) console.log(`[fingerprint] Failed to define ${property}: ${err.message}`);
|
|
344
|
+
return false;
|
|
815
345
|
}
|
|
816
|
-
}
|
|
346
|
+
}
|
|
817
347
|
|
|
818
|
-
|
|
819
|
-
|
|
348
|
+
// Safe execution wrapper
|
|
349
|
+
function safeExecute(fn, description) {
|
|
820
350
|
try {
|
|
821
|
-
|
|
351
|
+
fn();
|
|
352
|
+
return true;
|
|
822
353
|
} catch (err) {
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
};
|
|
826
|
-
|
|
827
|
-
// Override navigator.timezone related properties
|
|
828
|
-
if (navigator && !navigator.timezone) {
|
|
829
|
-
Object.defineProperty(navigator, 'timezone', {
|
|
830
|
-
get: () => 'America/New_York',
|
|
831
|
-
configurable: true
|
|
832
|
-
});
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
// Protect common timezone detection methods
|
|
836
|
-
window.getTimezone = window.getTimezone || (() => 'America/New_York');
|
|
837
|
-
window.getTimezoneOffset = window.getTimezoneOffset || (() => 300);
|
|
838
|
-
|
|
839
|
-
// Create jstz-like object (common timezone detection library)
|
|
840
|
-
window.jstz = window.jstz || {
|
|
841
|
-
determine: () => ({ name: () => 'America/New_York' }),
|
|
842
|
-
olson: { timezones: { 'America/New_York': true } }
|
|
843
|
-
};
|
|
844
|
-
|
|
845
|
-
// Create mock timezone object
|
|
846
|
-
window.tz = window.tz || {
|
|
847
|
-
name: 'America/New_York',
|
|
848
|
-
offset: -300,
|
|
849
|
-
abbr: 'EST',
|
|
850
|
-
dst: false,
|
|
851
|
-
getTimezoneOffset: () => 300
|
|
852
|
-
};
|
|
853
|
-
|
|
854
|
-
// Ensure Intl.DateTimeFormat resolvedOptions returns safe values
|
|
855
|
-
if (window.Intl && window.Intl.DateTimeFormat) {
|
|
856
|
-
const OriginalDateTimeFormat = window.Intl.DateTimeFormat;
|
|
857
|
-
window.Intl.DateTimeFormat = function(...args) {
|
|
858
|
-
const instance = new OriginalDateTimeFormat(...args);
|
|
859
|
-
const originalResolvedOptions = instance.resolvedOptions;
|
|
860
|
-
|
|
861
|
-
instance.resolvedOptions = function() {
|
|
862
|
-
try {
|
|
863
|
-
const options = originalResolvedOptions.call(this);
|
|
864
|
-
// Ensure timezone is always set to prevent null errors
|
|
865
|
-
if (!options.timeZone) {
|
|
866
|
-
options.timeZone = 'America/New_York';
|
|
867
|
-
}
|
|
868
|
-
return options;
|
|
869
|
-
} catch (err) {
|
|
870
|
-
// Return safe default options if resolvedOptions fails
|
|
871
|
-
return {
|
|
872
|
-
locale: 'en-US',
|
|
873
|
-
timeZone: 'America/New_York',
|
|
874
|
-
calendar: 'gregory',
|
|
875
|
-
numberingSystem: 'latn'
|
|
876
|
-
};
|
|
877
|
-
}
|
|
878
|
-
};
|
|
879
|
-
return instance;
|
|
880
|
-
};
|
|
881
|
-
|
|
882
|
-
// Copy static methods
|
|
883
|
-
Object.setPrototypeOf(window.Intl.DateTimeFormat, OriginalDateTimeFormat);
|
|
884
|
-
};
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
// JavaScript Library Protection - Create mock objects for common libraries
|
|
888
|
-
// jQuery protection
|
|
889
|
-
if (typeof window.$ === 'undefined') {
|
|
890
|
-
window.$ = function(selector) {
|
|
891
|
-
// Return a minimal jQuery-like object
|
|
892
|
-
return {
|
|
893
|
-
ready: function(fn) { if (typeof fn === 'function') setTimeout(fn, 0); return this; },
|
|
894
|
-
on: function() { return this; },
|
|
895
|
-
off: function() { return this; },
|
|
896
|
-
click: function() { return this; },
|
|
897
|
-
hide: function() { return this; },
|
|
898
|
-
show: function() { return this; },
|
|
899
|
-
css: function() { return this; },
|
|
900
|
-
attr: function() { return this; },
|
|
901
|
-
html: function() { return this; },
|
|
902
|
-
text: function() { return this; },
|
|
903
|
-
val: function() { return this; },
|
|
904
|
-
addClass: function() { return this; },
|
|
905
|
-
removeClass: function() { return this; },
|
|
906
|
-
length: 0,
|
|
907
|
-
each: function() { return this; }
|
|
908
|
-
};
|
|
909
|
-
};
|
|
910
|
-
// Common jQuery aliases
|
|
911
|
-
window.jQuery = window.$;
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
// Other common library protections
|
|
915
|
-
window._ = window._ || { // Lodash/Underscore
|
|
916
|
-
forEach: function() {},
|
|
917
|
-
map: function() { return []; },
|
|
918
|
-
filter: function() { return []; },
|
|
919
|
-
find: function() { return undefined; }
|
|
920
|
-
};
|
|
921
|
-
|
|
922
|
-
window.moment = window.moment || function() { // Moment.js
|
|
923
|
-
return {
|
|
924
|
-
format: function() { return new Date().toISOString(); },
|
|
925
|
-
valueOf: function() { return Date.now(); }
|
|
926
|
-
};
|
|
927
|
-
};
|
|
928
|
-
|
|
929
|
-
// Enhanced console error handling for library errors
|
|
930
|
-
const originalConsoleError = console.error;
|
|
931
|
-
console.error = function(...args) {
|
|
932
|
-
const message = args.join(' ');
|
|
933
|
-
// Suppress common library error messages
|
|
934
|
-
if (typeof message === 'string' && (
|
|
935
|
-
message.includes('$ is not defined') ||
|
|
936
|
-
message.includes('jQuery is not defined') ||
|
|
937
|
-
message.includes('_ is not defined') ||
|
|
938
|
-
message.includes('moment is not defined') ||
|
|
939
|
-
message.includes('bootstrap is not defined') ||
|
|
940
|
-
message.includes('is not a function') ||
|
|
941
|
-
message.includes('Cannot read property') ||
|
|
942
|
-
message.includes('Cannot read properties of undefined') ||
|
|
943
|
-
message.includes('Cannot read properties of null') ||
|
|
944
|
-
message.includes('.closest is not a function') ||
|
|
945
|
-
message.includes('.toLowerCase') ||
|
|
946
|
-
message.includes('is not valid JSON')
|
|
947
|
-
)) {
|
|
948
|
-
if (debugEnabled) {
|
|
949
|
-
console.log(`[fingerprint] Suppressed library error: ${message}`);
|
|
950
|
-
}
|
|
951
|
-
return; // Don't log the error
|
|
952
|
-
}
|
|
953
|
-
// For all other errors, use original console.error
|
|
954
|
-
return originalConsoleError.apply(this, arguments);
|
|
955
|
-
};
|
|
956
|
-
|
|
957
|
-
// Enhanced TZ Check Protection - Handle timezone validation functions
|
|
958
|
-
window.tzCheck = window.tzCheck || function() {
|
|
959
|
-
return 'America/New_York';
|
|
960
|
-
};
|
|
961
|
-
|
|
962
|
-
window.checkTimezone = window.checkTimezone || function() {
|
|
963
|
-
return true;
|
|
964
|
-
};
|
|
965
|
-
|
|
966
|
-
window.validateTimezone = window.validateTimezone || function() {
|
|
967
|
-
return { valid: true, timezone: 'America/New_York' };
|
|
968
|
-
};
|
|
969
|
-
|
|
970
|
-
// Mock common timezone libraries and their methods
|
|
971
|
-
window.momentTimezone = window.momentTimezone || {
|
|
972
|
-
tz: {
|
|
973
|
-
guess: () => 'America/New_York',
|
|
974
|
-
names: () => ['America/New_York'],
|
|
975
|
-
zone: () => ({ name: 'America/New_York', abbr: 'EST' })
|
|
976
|
-
}
|
|
977
|
-
};
|
|
978
|
-
|
|
979
|
-
// Enhanced Intl timezone protection
|
|
980
|
-
if (window.Intl) {
|
|
981
|
-
// Override supportedLocalesOf to always return safe values
|
|
982
|
-
if (window.Intl.DateTimeFormat && window.Intl.DateTimeFormat.supportedLocalesOf) {
|
|
983
|
-
const originalSupportedLocales = window.Intl.DateTimeFormat.supportedLocalesOf;
|
|
984
|
-
window.Intl.DateTimeFormat.supportedLocalesOf = function(locales, options) {
|
|
985
|
-
try {
|
|
986
|
-
return originalSupportedLocales.call(this, locales, options);
|
|
987
|
-
} catch (err) {
|
|
988
|
-
return ['en-US'];
|
|
989
|
-
}
|
|
990
|
-
};
|
|
354
|
+
if (debugEnabled) console.log(`[fingerprint] ${description} failed: ${err.message}`);
|
|
355
|
+
return false;
|
|
991
356
|
}
|
|
992
|
-
|
|
993
|
-
// Add resolvedOptions protection to all Intl objects
|
|
994
|
-
['DateTimeFormat', 'NumberFormat', 'Collator'].forEach(intlType => {
|
|
995
|
-
if (window.Intl[intlType]) {
|
|
996
|
-
const OriginalIntl = window.Intl[intlType];
|
|
997
|
-
window.Intl[intlType] = function(...args) {
|
|
998
|
-
try {
|
|
999
|
-
return new OriginalIntl(...args);
|
|
1000
|
-
} catch (err) {
|
|
1001
|
-
// Return basic mock object for any Intl constructor failures
|
|
1002
|
-
return {
|
|
1003
|
-
resolvedOptions: () => ({
|
|
1004
|
-
locale: 'en-US',
|
|
1005
|
-
timeZone: 'America/New_York',
|
|
1006
|
-
calendar: 'gregory'
|
|
1007
|
-
}),
|
|
1008
|
-
format: () => new Date().toLocaleDateString()
|
|
1009
|
-
};
|
|
1010
|
-
}
|
|
1011
|
-
};
|
|
1012
|
-
Object.setPrototypeOf(window.Intl[intlType], OriginalIntl);
|
|
1013
|
-
}
|
|
1014
|
-
});
|
|
1015
357
|
}
|
|
1016
|
-
|
|
1017
|
-
// Global error handler enhancement for timezone-specific errors
|
|
1018
|
-
const originalWindowError = window.onerror;
|
|
1019
|
-
window.onerror = function(message, source, lineno, colno, error) {
|
|
1020
|
-
// Handle timezone-specific errors
|
|
1021
|
-
if (typeof message === 'string' && (
|
|
1022
|
-
message.includes('tz check') ||
|
|
1023
|
-
message.includes('timezone check') ||
|
|
1024
|
-
message.includes('tz is not defined') ||
|
|
1025
|
-
message.includes('timezone is not defined') ||
|
|
1026
|
-
message.includes('Invalid timezone') ||
|
|
1027
|
-
message.includes('TimeZone') ||
|
|
1028
|
-
message.includes('getTimezoneOffset')
|
|
1029
|
-
)) {
|
|
1030
|
-
if (debugEnabled) {
|
|
1031
|
-
console.log(`[fingerprint] Suppressed timezone error: ${message}`);
|
|
1032
|
-
}
|
|
1033
|
-
return true; // Prevent the error from showing
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
// Call original error handler for non-timezone errors
|
|
1037
|
-
if (originalWindowError) {
|
|
1038
|
-
return originalWindowError.apply(this, arguments);
|
|
1039
|
-
}
|
|
1040
|
-
return false;
|
|
1041
|
-
};
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
// 1. Enhanced webdriver removal with safe descriptor manipulation
|
|
1046
|
-
safeExecuteSpoofing(() => {
|
|
1047
|
-
// Skip if navigator.webdriver is non-configurable
|
|
1048
|
-
if (!canModifyProperty(navigator, 'webdriver', debugEnabled)) {
|
|
1049
|
-
if (debugEnabled) {
|
|
1050
|
-
console.log('[fingerprint] Skipping non-configurable navigator.webdriver');
|
|
1051
|
-
}
|
|
1052
|
-
return;
|
|
1053
|
-
}
|
|
1054
358
|
|
|
359
|
+
// Remove webdriver properties
|
|
360
|
+
safeExecute(() => {
|
|
1055
361
|
try {
|
|
1056
362
|
delete navigator.webdriver;
|
|
1057
|
-
} catch (
|
|
1058
|
-
|
|
1059
|
-
try {
|
|
1060
|
-
navigator.webdriver = undefined;
|
|
1061
|
-
} catch (assignErr) {
|
|
1062
|
-
// Both deletion and assignment failed, skip
|
|
1063
|
-
if (debugEnabled) {
|
|
1064
|
-
console.log('[fingerprint] Cannot modify navigator.webdriver, skipping');
|
|
1065
|
-
}
|
|
1066
|
-
return;
|
|
1067
|
-
}
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
|
-
safeDefineProperty(navigator, 'webdriver', {
|
|
1071
|
-
get: () => undefined,
|
|
1072
|
-
enumerable: false,
|
|
1073
|
-
configurable: true
|
|
1074
|
-
}, debugEnabled);
|
|
363
|
+
} catch (e) {}
|
|
364
|
+
safeDefinePropertyLocal(navigator, 'webdriver', { get: () => undefined });
|
|
1075
365
|
}, 'webdriver removal');
|
|
1076
|
-
|
|
1077
|
-
// 2. Enhanced automation detection removal with safe handling
|
|
1078
|
-
const automationProps = [
|
|
1079
|
-
'callPhantom', '_phantom', '__nightmare', '_selenium',
|
|
1080
|
-
'__selenium_unwrapped', '__webdriver_evaluate', '__driver_evaluate',
|
|
1081
|
-
'__webdriver_script_function', '__webdriver_script_func',
|
|
1082
|
-
'__webdriver_script_fn', '__fxdriver_evaluate', '__driver_unwrapped',
|
|
1083
|
-
'__webdriver_unwrapped', '__selenium_evaluate', '__fxdriver_unwrapped',
|
|
1084
|
-
'spawn', 'emit', 'Buffer', 'domAutomation',
|
|
1085
|
-
'domAutomationController', '__lastWatirAlert', '__lastWatirConfirm',
|
|
1086
|
-
'__lastWatirPrompt', '_Selenium_IDE_Recorder', '_selenium', 'calledSelenium'
|
|
1087
|
-
];
|
|
1088
|
-
|
|
1089
|
-
safeExecuteSpoofing(() => {
|
|
1090
|
-
automationProps.forEach(prop => {
|
|
1091
|
-
|
|
1092
|
-
// Debug: Log which property is being processed
|
|
1093
|
-
if (debugEnabled && (prop === 'href' || prop.includes('href'))) {
|
|
1094
|
-
console.log(`[fingerprint] WARNING: Processing href-related property: ${prop}`);
|
|
1095
|
-
}
|
|
1096
|
-
try {
|
|
1097
|
-
// Skip built-in properties immediately
|
|
1098
|
-
if (['href', 'origin', 'protocol', 'host', 'hostname', 'port', 'pathname', 'search', 'hash', 'constructor', 'prototype', '__proto__', 'toString', 'valueOf', 'assign', 'reload', 'replace'].includes(prop)) {
|
|
1099
|
-
if (debugEnabled) {
|
|
1100
|
-
console.log(`[fingerprint] Skipping built-in automation property: ${prop}`);
|
|
1101
|
-
}
|
|
1102
|
-
return;
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
|
-
// Additional safety check specifically for href
|
|
1106
|
-
if (prop === 'href') {
|
|
1107
|
-
if (debugEnabled) console.log(`[fingerprint] BLOCKING href property modification attempt`);
|
|
1108
|
-
return;
|
|
1109
|
-
}
|
|
1110
366
|
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
enumerable: false
|
|
1127
|
-
}, debugEnabled);
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
if (canModifyProperty(navigator, prop, debugEnabled)) {
|
|
1131
|
-
safeDefineProperty(navigator, prop, {
|
|
1132
|
-
get: () => undefined,
|
|
1133
|
-
enumerable: false
|
|
1134
|
-
}, debugEnabled);
|
|
1135
|
-
}
|
|
1136
|
-
} catch (propErr) {
|
|
1137
|
-
// Skip problematic properties
|
|
1138
|
-
if (debugEnabled) {
|
|
1139
|
-
console.log(`[fingerprint] Skipped automation property ${prop}: ${propErr.message}`);
|
|
1140
|
-
}
|
|
1141
|
-
}
|
|
367
|
+
// Remove automation properties
|
|
368
|
+
safeExecute(() => {
|
|
369
|
+
const automationProps = [
|
|
370
|
+
'callPhantom', '_phantom', '__nightmare', '_selenium', '__selenium_unwrapped',
|
|
371
|
+
'__webdriver_evaluate', '__driver_evaluate', '__webdriver_script_function',
|
|
372
|
+
'spawn', 'emit', 'Buffer', 'domAutomation', 'domAutomationController'
|
|
373
|
+
];
|
|
374
|
+
|
|
375
|
+
automationProps.forEach(prop => {
|
|
376
|
+
try {
|
|
377
|
+
delete window[prop];
|
|
378
|
+
delete navigator[prop];
|
|
379
|
+
safeDefinePropertyLocal(window, prop, { get: () => undefined });
|
|
380
|
+
safeDefinePropertyLocal(navigator, prop, { get: () => undefined });
|
|
381
|
+
} catch (e) {}
|
|
1142
382
|
});
|
|
1143
383
|
}, 'automation properties removal');
|
|
1144
384
|
|
|
1145
|
-
//
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
if (!window.chrome || !window.chrome.runtime) {
|
|
385
|
+
// Simulate Chrome runtime
|
|
386
|
+
safeExecute(() => {
|
|
387
|
+
if (!window.chrome?.runtime) {
|
|
1149
388
|
window.chrome = {
|
|
1150
389
|
runtime: {
|
|
1151
390
|
onConnect: { addListener: () => {}, removeListener: () => {} },
|
|
1152
391
|
onMessage: { addListener: () => {}, removeListener: () => {} },
|
|
1153
392
|
sendMessage: () => {},
|
|
1154
|
-
connect: () => ({
|
|
1155
|
-
|
|
1156
|
-
postMessage: () => {},
|
|
1157
|
-
disconnect: () => {}
|
|
1158
|
-
}),
|
|
1159
|
-
getManifest: () => ({
|
|
1160
|
-
name: "Chrome",
|
|
1161
|
-
version: "131.0.0.0"
|
|
1162
|
-
}),
|
|
393
|
+
connect: () => ({ onMessage: { addListener: () => {}, removeListener: () => {} }, postMessage: () => {}, disconnect: () => {} }),
|
|
394
|
+
getManifest: () => ({ name: "Chrome", version: "131.0.0.0" }),
|
|
1163
395
|
getURL: (path) => `chrome-extension://invalid/${path}`,
|
|
1164
396
|
id: undefined
|
|
1165
397
|
},
|
|
1166
|
-
loadTimes: () =>
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
startLoadTime: performance.now() - Math.random() * 1500,
|
|
1177
|
-
wasAlternateProtocolAvailable: false,
|
|
1178
|
-
wasFetchedViaSpdy: false,
|
|
1179
|
-
wasNpnNegotiated: false
|
|
1180
|
-
}),
|
|
398
|
+
loadTimes: () => {
|
|
399
|
+
const now = performance.now();
|
|
400
|
+
return {
|
|
401
|
+
commitLoadTime: now - Math.random() * 1000,
|
|
402
|
+
connectionInfo: 'http/1.1',
|
|
403
|
+
finishDocumentLoadTime: now - Math.random() * 500,
|
|
404
|
+
finishLoadTime: now - Math.random() * 100,
|
|
405
|
+
navigationType: 'Navigation'
|
|
406
|
+
};
|
|
407
|
+
},
|
|
1181
408
|
csi: () => ({
|
|
1182
409
|
onloadT: Date.now(),
|
|
1183
410
|
pageT: Math.random() * 1000,
|
|
1184
|
-
startE: Date.now() - Math.random() * 2000
|
|
1185
|
-
|
|
1186
|
-
}),
|
|
1187
|
-
app: {
|
|
1188
|
-
isInstalled: false,
|
|
1189
|
-
InstallState: { DISABLED: 'disabled', INSTALLED: 'installed', NOT_INSTALLED: 'not_installed' },
|
|
1190
|
-
RunningState: { CANNOT_RUN: 'cannot_run', READY_TO_RUN: 'ready_to_run', RUNNING: 'running' }
|
|
1191
|
-
}
|
|
411
|
+
startE: Date.now() - Math.random() * 2000
|
|
412
|
+
})
|
|
1192
413
|
};
|
|
1193
414
|
}
|
|
1194
415
|
}, 'Chrome runtime simulation');
|
|
1195
|
-
|
|
1196
|
-
//
|
|
1197
|
-
|
|
1198
|
-
const isChrome = userAgent.includes('Chrome');
|
|
1199
|
-
const isFirefox = userAgent.includes('Firefox');
|
|
1200
|
-
const isSafari = userAgent.includes('Safari') && !userAgent.includes('Chrome');
|
|
1201
|
-
|
|
416
|
+
|
|
417
|
+
// Spoof plugins based on user agent
|
|
418
|
+
safeExecute(() => {
|
|
1202
419
|
let plugins = [];
|
|
1203
|
-
if (
|
|
1204
|
-
plugins = [
|
|
1205
|
-
{ name: 'Chrome PDF Plugin', length: 1, description: 'Portable Document Format', filename: 'internal-pdf-viewer' },
|
|
1206
|
-
{ name: 'Chrome PDF Viewer', length: 1, description: 'PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai' },
|
|
1207
|
-
{ name: 'Native Client', length: 2, description: 'Native Client Executable', filename: 'internal-nacl-plugin' }
|
|
1208
|
-
];
|
|
1209
|
-
} else if (isFirefox) {
|
|
420
|
+
if (userAgent.includes('Chrome')) {
|
|
1210
421
|
plugins = [
|
|
1211
|
-
{ name: 'PDF
|
|
422
|
+
{ name: 'Chrome PDF Plugin', description: 'Portable Document Format', filename: 'internal-pdf-viewer' },
|
|
423
|
+
{ name: 'Chrome PDF Viewer', description: 'PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai' }
|
|
1212
424
|
];
|
|
1213
|
-
} else if (
|
|
425
|
+
} else if (userAgent.includes('Firefox')) {
|
|
1214
426
|
plugins = [
|
|
1215
|
-
{ name: '
|
|
427
|
+
{ name: 'PDF.js', description: 'Portable Document Format', filename: 'internal-pdf-js' }
|
|
1216
428
|
];
|
|
1217
429
|
}
|
|
1218
|
-
|
|
1219
|
-
safeDefineProperty(navigator, 'plugins', {
|
|
1220
|
-
get: () => plugins
|
|
1221
|
-
}, debugEnabled);
|
|
430
|
+
safeDefinePropertyLocal(navigator, 'plugins', { get: () => plugins });
|
|
1222
431
|
}, 'plugins spoofing');
|
|
1223
|
-
|
|
1224
|
-
//
|
|
1225
|
-
|
|
432
|
+
|
|
433
|
+
// Spoof languages
|
|
434
|
+
safeExecute(() => {
|
|
1226
435
|
const languages = ['en-US', 'en'];
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
get: () => languages
|
|
1230
|
-
}, debugEnabled);
|
|
1231
|
-
|
|
1232
|
-
safeDefineProperty(navigator, 'language', {
|
|
1233
|
-
get: () => languages[0]
|
|
1234
|
-
}, debugEnabled);
|
|
436
|
+
safeDefinePropertyLocal(navigator, 'languages', { get: () => languages });
|
|
437
|
+
safeDefinePropertyLocal(navigator, 'language', { get: () => languages[0] });
|
|
1235
438
|
}, 'language spoofing');
|
|
1236
|
-
|
|
1237
|
-
//
|
|
1238
|
-
|
|
1239
|
-
const isFirefox = userAgent.includes('Firefox');
|
|
1240
|
-
const isSafari = userAgent.includes('Safari') && !userAgent.includes('Chrome');
|
|
1241
|
-
|
|
439
|
+
|
|
440
|
+
// Spoof vendor information
|
|
441
|
+
safeExecute(() => {
|
|
1242
442
|
let vendor = 'Google Inc.';
|
|
1243
443
|
let product = 'Gecko';
|
|
1244
444
|
|
|
1245
|
-
if (
|
|
445
|
+
if (userAgent.includes('Firefox')) {
|
|
1246
446
|
vendor = '';
|
|
1247
|
-
|
|
1248
|
-
} else if (isSafari) {
|
|
447
|
+
} else if (userAgent.includes('Safari') && !userAgent.includes('Chrome')) {
|
|
1249
448
|
vendor = 'Apple Computer, Inc.';
|
|
1250
|
-
product = 'Gecko';
|
|
1251
449
|
}
|
|
1252
450
|
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
}, debugEnabled);
|
|
1256
|
-
|
|
1257
|
-
safeDefineProperty(navigator, 'product', {
|
|
1258
|
-
get: () => product
|
|
1259
|
-
}, debugEnabled);
|
|
451
|
+
safeDefinePropertyLocal(navigator, 'vendor', { get: () => vendor });
|
|
452
|
+
safeDefinePropertyLocal(navigator, 'product', { get: () => product });
|
|
1260
453
|
}, 'vendor/product spoofing');
|
|
1261
|
-
|
|
1262
|
-
//
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
{ type: 'application/pdf', description: 'Portable Document Format', suffixes: 'pdf', enabledPlugin: navigator.plugins[0] },
|
|
1271
|
-
{ type: 'application/x-google-chrome-pdf', description: 'Portable Document Format', suffixes: 'pdf', enabledPlugin: navigator.plugins[1] },
|
|
1272
|
-
{ type: 'application/x-nacl', description: 'Native Client Executable', suffixes: '', enabledPlugin: navigator.plugins[2] }
|
|
1273
|
-
];
|
|
1274
|
-
}
|
|
1275
|
-
return [];
|
|
1276
|
-
}
|
|
1277
|
-
}, debugEnabled);
|
|
1278
|
-
}, 'mimeTypes spoofing');
|
|
1279
|
-
|
|
1280
|
-
// 8. Enhanced permission API spoofing with safe handling
|
|
1281
|
-
safeExecuteSpoofing(() => {
|
|
1282
|
-
if (navigator.permissions && navigator.permissions.query) {
|
|
1283
|
-
const originalQuery = navigator.permissions.query;
|
|
1284
|
-
navigator.permissions.query = function(parameters) {
|
|
1285
|
-
const granted = ['camera', 'microphone', 'notifications'];
|
|
1286
|
-
const denied = ['midi', 'push', 'speaker'];
|
|
1287
|
-
const prompt = ['geolocation'];
|
|
1288
|
-
|
|
1289
|
-
if (granted.includes(parameters.name)) {
|
|
1290
|
-
return Promise.resolve({ state: 'granted', onchange: null });
|
|
1291
|
-
} else if (denied.includes(parameters.name)) {
|
|
1292
|
-
return Promise.resolve({ state: 'denied', onchange: null });
|
|
1293
|
-
} else if (prompt.includes(parameters.name)) {
|
|
1294
|
-
return Promise.resolve({ state: 'prompt', onchange: null });
|
|
1295
|
-
}
|
|
1296
|
-
return originalQuery.apply(this, arguments);
|
|
1297
|
-
};
|
|
454
|
+
|
|
455
|
+
// Spoof MIME types
|
|
456
|
+
safeExecute(() => {
|
|
457
|
+
let mimeTypes = [];
|
|
458
|
+
if (userAgent.includes('Chrome')) {
|
|
459
|
+
mimeTypes = [
|
|
460
|
+
{ type: 'application/pdf', description: 'Portable Document Format', suffixes: 'pdf' },
|
|
461
|
+
{ type: 'application/x-google-chrome-pdf', description: 'Portable Document Format', suffixes: 'pdf' }
|
|
462
|
+
];
|
|
1298
463
|
}
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
464
|
+
safeDefinePropertyLocal(navigator, 'mimeTypes', { get: () => mimeTypes });
|
|
465
|
+
}, 'mimeTypes spoofing');
|
|
466
|
+
|
|
467
|
+
// Enhanced Error.stack protection for CDP detection
|
|
468
|
+
safeExecute(() => {
|
|
469
|
+
const OriginalError = window.Error;
|
|
470
|
+
window.Error = function(...args) {
|
|
471
|
+
const error = new OriginalError(...args);
|
|
472
|
+
const originalStack = error.stack;
|
|
473
|
+
|
|
474
|
+
Object.defineProperty(error, 'stack', {
|
|
1306
475
|
get: function() {
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
automationProps.forEach(prop => {
|
|
1315
|
-
try {
|
|
1316
|
-
// Use the same built-in property check for iframe context
|
|
1317
|
-
if (!canModifyProperty(win, prop, debugEnabled)) return;
|
|
1318
|
-
delete win[prop];
|
|
1319
|
-
safeDefineProperty(win, prop, {
|
|
1320
|
-
get: () => undefined,
|
|
1321
|
-
enumerable: false
|
|
1322
|
-
}, debugEnabled);
|
|
1323
|
-
} catch(e) {}
|
|
1324
|
-
});
|
|
1325
|
-
}
|
|
1326
|
-
return win;
|
|
1327
|
-
}
|
|
1328
|
-
}, debugEnabled);
|
|
1329
|
-
}
|
|
1330
|
-
}, 'iframe contentWindow spoofing');
|
|
1331
|
-
|
|
1332
|
-
// 10. Enhanced connection information spoofing with safe handling
|
|
1333
|
-
safeExecuteSpoofing(() => {
|
|
1334
|
-
if (navigator.connection) {
|
|
1335
|
-
// Check if connection properties can be modified
|
|
1336
|
-
try {
|
|
1337
|
-
const connectionTest = Object.getOwnPropertyDescriptor(navigator.connection, 'rtt');
|
|
1338
|
-
if (connectionTest && connectionTest.configurable === false) {
|
|
1339
|
-
if (debugEnabled) {
|
|
1340
|
-
console.log('[fingerprint] Connection properties are non-configurable, skipping');
|
|
476
|
+
if (typeof originalStack === 'string') {
|
|
477
|
+
return originalStack
|
|
478
|
+
.replace(/.*puppeteer.*\n?/gi, '')
|
|
479
|
+
.replace(/.*chrome-devtools.*\n?/gi, '')
|
|
480
|
+
.replace(/.*webdriver.*\n?/gi, '')
|
|
481
|
+
.replace(/.*automation.*\n?/gi, '')
|
|
482
|
+
.trim() || `${this.name || 'Error'}: ${this.message || ''}\n at unknown location`;
|
|
1341
483
|
}
|
|
1342
|
-
return;
|
|
1343
|
-
}
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
484
|
+
return originalStack;
|
|
485
|
+
},
|
|
486
|
+
configurable: true
|
|
487
|
+
});
|
|
488
|
+
return error;
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
window.Error.prototype = OriginalError.prototype;
|
|
492
|
+
Object.setPrototypeOf(window.Error, OriginalError);
|
|
493
|
+
|
|
494
|
+
// Copy static properties
|
|
495
|
+
['captureStackTrace', 'stackTraceLimit', 'prepareStackTrace'].forEach(prop => {
|
|
496
|
+
if (OriginalError[prop]) {
|
|
497
|
+
try { window.Error[prop] = OriginalError[prop]; } catch (e) {}
|
|
1349
498
|
}
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
}
|
|
1362
|
-
}
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
499
|
+
});
|
|
500
|
+
}, 'Error stack protection');
|
|
501
|
+
|
|
502
|
+
// Create fingerprinting mock objects
|
|
503
|
+
safeExecute(() => {
|
|
504
|
+
const mockResult = {
|
|
505
|
+
visitorId: 'mock_visitor_' + Math.random().toString(36).substr(2, 9),
|
|
506
|
+
confidence: { score: 0.99 },
|
|
507
|
+
components: {
|
|
508
|
+
screen: { value: { width: 1920, height: 1080 } },
|
|
509
|
+
timezone: { value: 'America/New_York' }
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
window.fp = window.fp || {
|
|
514
|
+
getResult: (callback) => callback ? setTimeout(() => callback(mockResult), 0) : mockResult,
|
|
515
|
+
get: (callback) => Promise.resolve(mockResult),
|
|
516
|
+
load: () => Promise.resolve(window.fp)
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
window.FingerprintJS = window.FingerprintJS || {
|
|
520
|
+
load: () => Promise.resolve({ get: () => Promise.resolve(mockResult) })
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
window.ClientJS = window.ClientJS || function() {
|
|
524
|
+
this.getFingerprint = () => 'mock_fingerprint_' + Math.random().toString(36).substr(2, 9);
|
|
525
|
+
};
|
|
526
|
+
}, 'fingerprinting mocks');
|
|
527
|
+
|
|
528
|
+
// WebGL spoofing
|
|
529
|
+
safeExecute(() => {
|
|
1367
530
|
if (window.WebGLRenderingContext) {
|
|
1368
531
|
const getParameter = WebGLRenderingContext.prototype.getParameter;
|
|
1369
532
|
WebGLRenderingContext.prototype.getParameter = function(parameter) {
|
|
1370
|
-
if (parameter === 37445)
|
|
1371
|
-
|
|
1372
|
-
}
|
|
1373
|
-
if (parameter === 37446) { // UNMASKED_RENDERER_WEBGL
|
|
1374
|
-
return 'Intel Iris OpenGL Engine';
|
|
1375
|
-
}
|
|
533
|
+
if (parameter === 37445) return 'Intel Inc.';
|
|
534
|
+
if (parameter === 37446) return 'Intel Iris OpenGL Engine';
|
|
1376
535
|
return getParameter.call(this, parameter);
|
|
1377
536
|
};
|
|
1378
537
|
}
|
|
1379
|
-
}, 'WebGL
|
|
1380
|
-
|
|
1381
|
-
//
|
|
1382
|
-
|
|
538
|
+
}, 'WebGL spoofing');
|
|
539
|
+
|
|
540
|
+
// Canvas fingerprinting protection
|
|
541
|
+
safeExecute(() => {
|
|
1383
542
|
const originalToDataURL = HTMLCanvasElement.prototype.toDataURL;
|
|
1384
543
|
HTMLCanvasElement.prototype.toDataURL = function(...args) {
|
|
1385
544
|
const context = this.getContext('2d');
|
|
1386
545
|
if (context) {
|
|
1387
|
-
// Add subtle noise to canvas to prevent fingerprinting
|
|
1388
546
|
const imageData = context.getImageData(0, 0, this.width, this.height);
|
|
1389
547
|
for (let i = 0; i < imageData.data.length; i += 4) {
|
|
1390
548
|
imageData.data[i] = imageData.data[i] + Math.floor(Math.random() * 3) - 1;
|
|
@@ -1393,361 +551,87 @@ async function applyUserAgentSpoofing(page, siteConfig, forceDebug, currentUrl)
|
|
|
1393
551
|
}
|
|
1394
552
|
return originalToDataURL.apply(this, args);
|
|
1395
553
|
};
|
|
1396
|
-
}, 'canvas fingerprinting');
|
|
1397
|
-
|
|
1398
|
-
//
|
|
1399
|
-
|
|
1400
|
-
if (Error.captureStackTrace) {
|
|
1401
|
-
const originalCaptureStackTrace = Error.captureStackTrace;
|
|
1402
|
-
Error.captureStackTrace = function(targetObject, constructorOpt) {
|
|
1403
|
-
const result = originalCaptureStackTrace.call(this, targetObject, constructorOpt);
|
|
1404
|
-
if (targetObject.stack) {
|
|
1405
|
-
// Remove puppeteer-related stack traces
|
|
1406
|
-
targetObject.stack = targetObject.stack
|
|
1407
|
-
.split('\n')
|
|
1408
|
-
.filter(line => !line.includes('puppeteer') && !line.includes('DevTools') && !line.includes('chrome-devtools'))
|
|
1409
|
-
.join('\n');
|
|
1410
|
-
}
|
|
1411
|
-
return result;
|
|
1412
|
-
};
|
|
1413
|
-
}
|
|
1414
|
-
}, 'stack trace cleaning');
|
|
1415
|
-
|
|
1416
|
-
// 14. Patch toString methods with safe handling
|
|
1417
|
-
safeExecuteSpoofing(() => {
|
|
1418
|
-
Function.prototype.toString = new Proxy(Function.prototype.toString, {
|
|
1419
|
-
apply: function(target, thisArg, argumentsList) {
|
|
1420
|
-
const result = target.apply(thisArg, argumentsList);
|
|
1421
|
-
return result.replace(/puppeteer/gi, 'browser').replace(/headless/gi, 'chrome');
|
|
1422
|
-
}
|
|
1423
|
-
});
|
|
1424
|
-
}, 'toString method patching');
|
|
1425
|
-
|
|
1426
|
-
// 15. Spoof battery API with safe handling
|
|
1427
|
-
safeExecuteSpoofing(() => {
|
|
554
|
+
}, 'canvas fingerprinting protection');
|
|
555
|
+
|
|
556
|
+
// Battery API spoofing
|
|
557
|
+
safeExecute(() => {
|
|
1428
558
|
if (navigator.getBattery) {
|
|
1429
|
-
const originalGetBattery = navigator.getBattery;
|
|
1430
559
|
navigator.getBattery = function() {
|
|
1431
560
|
return Promise.resolve({
|
|
1432
561
|
charging: Math.random() > 0.5,
|
|
1433
562
|
chargingTime: Math.random() > 0.5 ? Infinity : Math.random() * 3600,
|
|
1434
563
|
dischargingTime: Math.random() * 7200,
|
|
1435
|
-
level: Math.random() * 0.99 + 0.01
|
|
1436
|
-
addEventListener: () => {},
|
|
1437
|
-
removeEventListener: () => {},
|
|
1438
|
-
dispatchEvent: () => true
|
|
564
|
+
level: Math.random() * 0.99 + 0.01
|
|
1439
565
|
});
|
|
1440
566
|
};
|
|
1441
567
|
}
|
|
1442
568
|
}, 'battery API spoofing');
|
|
1443
|
-
|
|
1444
|
-
// 16. Add realistic timing to console methods with safe handling
|
|
1445
|
-
safeExecuteSpoofing(() => {
|
|
1446
|
-
['debug', 'error', 'info', 'log', 'warn'].forEach(method => {
|
|
1447
|
-
const original = console[method];
|
|
1448
|
-
console[method] = function(...args) {
|
|
1449
|
-
// Add tiny random delay to mimic human-like console timing
|
|
1450
|
-
setTimeout(() => original.apply(console, args), Math.random() * 5);
|
|
1451
|
-
};
|
|
1452
|
-
});
|
|
1453
|
-
}, 'console timing');
|
|
1454
|
-
|
|
1455
|
-
// 18. Enhanced service worker handling to prevent registration errors
|
|
1456
|
-
safeExecuteSpoofing(() => {
|
|
1457
|
-
if ('serviceWorker' in navigator) {
|
|
1458
|
-
const originalRegister = navigator.serviceWorker.register;
|
|
1459
|
-
const originalGetRegistration = navigator.serviceWorker.getRegistration;
|
|
1460
|
-
const originalGetRegistrations = navigator.serviceWorker.getRegistrations;
|
|
1461
|
-
|
|
1462
|
-
// Wrap register method to handle errors gracefully
|
|
1463
|
-
navigator.serviceWorker.register = function(scriptURL, options) {
|
|
1464
|
-
try {
|
|
1465
|
-
if (debugEnabled) {
|
|
1466
|
-
console.log('[fingerprint] Service worker registration intercepted:', scriptURL);
|
|
1467
|
-
}
|
|
1468
|
-
|
|
1469
|
-
// Return a resolved promise to prevent registration errors
|
|
1470
|
-
return Promise.resolve({
|
|
1471
|
-
installing: null,
|
|
1472
|
-
waiting: null,
|
|
1473
|
-
active: {
|
|
1474
|
-
scriptURL: scriptURL,
|
|
1475
|
-
state: 'activated'
|
|
1476
|
-
},
|
|
1477
|
-
scope: options?.scope || '/',
|
|
1478
|
-
update: () => Promise.resolve(),
|
|
1479
|
-
unregister: () => Promise.resolve(true),
|
|
1480
|
-
addEventListener: () => {},
|
|
1481
|
-
removeEventListener: () => {},
|
|
1482
|
-
dispatchEvent: () => true
|
|
1483
|
-
});
|
|
1484
|
-
} catch (registerErr) {
|
|
1485
|
-
if (debugEnabled) {
|
|
1486
|
-
console.log('[fingerprint] Service worker register error handled:', registerErr.message);
|
|
1487
|
-
}
|
|
1488
|
-
// Return rejected promise to maintain normal error flow for sites that expect it
|
|
1489
|
-
return Promise.reject(new Error('ServiceWorker registration failed'));
|
|
1490
|
-
}
|
|
1491
|
-
};
|
|
1492
|
-
|
|
1493
|
-
// Wrap getRegistration to return mock registration
|
|
1494
|
-
navigator.serviceWorker.getRegistration = function(scope) {
|
|
1495
|
-
try {
|
|
1496
|
-
return Promise.resolve(null); // No existing registrations
|
|
1497
|
-
} catch (getRegErr) {
|
|
1498
|
-
return Promise.reject(getRegErr);
|
|
1499
|
-
}
|
|
1500
|
-
};
|
|
1501
|
-
|
|
1502
|
-
// Wrap getRegistrations to return empty array
|
|
1503
|
-
navigator.serviceWorker.getRegistrations = function() {
|
|
1504
|
-
try {
|
|
1505
|
-
return Promise.resolve([]); // No existing registrations
|
|
1506
|
-
} catch (getRegsErr) {
|
|
1507
|
-
return Promise.reject(getRegsErr);
|
|
1508
|
-
}
|
|
1509
|
-
};
|
|
1510
|
-
}
|
|
1511
|
-
}, 'service worker handling');
|
|
1512
569
|
|
|
1513
|
-
//
|
|
1514
|
-
|
|
1515
|
-
// HTTP status code error suppression only
|
|
570
|
+
// Suppress common console errors
|
|
571
|
+
safeExecute(() => {
|
|
1516
572
|
const originalConsoleError = console.error;
|
|
1517
573
|
console.error = function(...args) {
|
|
1518
|
-
const message = args.join(' ')
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
if (debugEnabled) {
|
|
1526
|
-
console.log(`[fingerprint] Suppressed HTTP status error: ${message}`);
|
|
1527
|
-
}
|
|
1528
|
-
return; // Suppress the error
|
|
1529
|
-
}
|
|
1530
|
-
// For all other errors, use original console.error
|
|
1531
|
-
return originalConsoleError.apply(this, arguments);
|
|
1532
|
-
};
|
|
1533
|
-
|
|
1534
|
-
// Safely wrap fetch to prevent network errors from propagating to page
|
|
1535
|
-
if (window.fetch) {
|
|
1536
|
-
const originalFetch = window.fetch;
|
|
1537
|
-
window.fetch = function(...args) {
|
|
1538
|
-
try {
|
|
1539
|
-
const result = originalFetch.apply(this, args);
|
|
1540
|
-
|
|
1541
|
-
// Handle fetch promise rejections to prevent uncaught network errors
|
|
1542
|
-
if (result && typeof result.catch === 'function') {
|
|
1543
|
-
return result.catch(fetchErr => {
|
|
1544
|
-
// Log network errors silently without throwing to page
|
|
1545
|
-
if (debugEnabled && fetchErr.name === 'TypeError' && fetchErr.message.includes('fetch')) {
|
|
1546
|
-
console.log('[fingerprint] Fetch network error handled:', fetchErr.message);
|
|
1547
|
-
}
|
|
1548
|
-
// Re-throw the error so normal error handling still works
|
|
1549
|
-
throw fetchErr;
|
|
1550
|
-
});
|
|
1551
|
-
}
|
|
1552
|
-
|
|
1553
|
-
return result;
|
|
1554
|
-
} catch (fetchWrapErr) {
|
|
1555
|
-
if (debugEnabled) {
|
|
1556
|
-
console.log('[fingerprint] Fetch wrapper error:', fetchWrapErr.message);
|
|
1557
|
-
}
|
|
1558
|
-
return originalFetch.apply(this, args);
|
|
1559
|
-
}
|
|
1560
|
-
};
|
|
1561
|
-
|
|
1562
|
-
// Preserve fetch properties
|
|
1563
|
-
Object.setPrototypeOf(window.fetch, originalFetch);
|
|
1564
|
-
}
|
|
1565
|
-
|
|
1566
|
-
// Safely wrap XMLHttpRequest to prevent network errors
|
|
1567
|
-
if (window.XMLHttpRequest) {
|
|
1568
|
-
const OriginalXHR = window.XMLHttpRequest;
|
|
1569
|
-
|
|
1570
|
-
window.XMLHttpRequest = function() {
|
|
1571
|
-
const xhr = new OriginalXHR();
|
|
1572
|
-
const originalOpen = xhr.open;
|
|
1573
|
-
const originalSend = xhr.send;
|
|
1574
|
-
|
|
1575
|
-
// Wrap open method
|
|
1576
|
-
xhr.open = function(...args) {
|
|
1577
|
-
try {
|
|
1578
|
-
return originalOpen.apply(this, args);
|
|
1579
|
-
} catch (openErr) {
|
|
1580
|
-
if (debugEnabled) {
|
|
1581
|
-
console.log('[fingerprint] XHR open error handled:', openErr.message);
|
|
1582
|
-
}
|
|
1583
|
-
throw openErr;
|
|
1584
|
-
}
|
|
1585
|
-
};
|
|
1586
|
-
|
|
1587
|
-
// Wrap send method
|
|
1588
|
-
xhr.send = function(...args) {
|
|
1589
|
-
try {
|
|
1590
|
-
return originalSend.apply(this, args);
|
|
1591
|
-
} catch (sendErr) {
|
|
1592
|
-
if (debugEnabled) {
|
|
1593
|
-
console.log('[fingerprint] XHR send error handled:', sendErr.message);
|
|
1594
|
-
}
|
|
1595
|
-
throw sendErr;
|
|
1596
|
-
}
|
|
1597
|
-
};
|
|
1598
|
-
|
|
1599
|
-
// Add error event listener to prevent uncaught network errors
|
|
1600
|
-
xhr.addEventListener('error', function(event) {
|
|
1601
|
-
if (debugEnabled) {
|
|
1602
|
-
console.log('[fingerprint] XHR network error event handled');
|
|
1603
|
-
}
|
|
1604
|
-
// Don't prevent default - let normal error handling work
|
|
1605
|
-
});
|
|
1606
|
-
|
|
1607
|
-
return xhr;
|
|
1608
|
-
};
|
|
1609
|
-
|
|
1610
|
-
// Preserve XMLHttpRequest properties
|
|
1611
|
-
Object.setPrototypeOf(window.XMLHttpRequest, OriginalXHR);
|
|
1612
|
-
window.XMLHttpRequest.prototype = OriginalXHR.prototype;
|
|
1613
|
-
}
|
|
1614
|
-
|
|
1615
|
-
// Global error handler for HTTP status code errors only
|
|
1616
|
-
const originalErrorHandler = window.onerror;
|
|
1617
|
-
window.onerror = function(message, source, lineno, colno, error) {
|
|
1618
|
-
const messageStr = String(message || '');
|
|
1619
|
-
|
|
1620
|
-
// Only handle HTTP status code errors
|
|
1621
|
-
const isHttpStatusError = /Failed to load resource.*server responded with a status of [45]\d{2}(\s*\(\))?/i.test(messageStr);
|
|
1622
|
-
|
|
1623
|
-
if (isHttpStatusError) {
|
|
1624
|
-
if (debugEnabled) {
|
|
1625
|
-
console.log(`[fingerprint] HTTP status error handled by global handler: ${message}`);
|
|
1626
|
-
}
|
|
1627
|
-
// Return true to suppress the error
|
|
1628
|
-
return true;
|
|
1629
|
-
}
|
|
1630
|
-
|
|
1631
|
-
// Call original error handler for all other errors
|
|
1632
|
-
if (originalErrorHandler) {
|
|
1633
|
-
return originalErrorHandler.apply(this, arguments);
|
|
1634
|
-
}
|
|
1635
|
-
|
|
1636
|
-
return false;
|
|
1637
|
-
};
|
|
1638
|
-
|
|
1639
|
-
// Unhandled promise rejection handler for HTTP status errors only
|
|
1640
|
-
const originalUnhandledRejection = window.onunhandledrejection;
|
|
1641
|
-
window.onunhandledrejection = function(event) {
|
|
1642
|
-
const reason = event.reason;
|
|
1643
|
-
let shouldSuppress = false;
|
|
1644
|
-
|
|
1645
|
-
if (reason) {
|
|
1646
|
-
const reasonMessage = String(reason.message || reason || '');
|
|
1647
|
-
// Only suppress HTTP status code related promise rejections
|
|
1648
|
-
shouldSuppress = /Failed to load resource.*server responded with a status of [45]\d{2}(\s*\(\))?/i.test(reasonMessage);
|
|
1649
|
-
}
|
|
1650
|
-
|
|
1651
|
-
if (shouldSuppress) {
|
|
1652
|
-
if (debugEnabled) {
|
|
1653
|
-
console.log('[fingerprint] HTTP status promise rejection handled:', reason);
|
|
1654
|
-
}
|
|
1655
|
-
event.preventDefault();
|
|
574
|
+
const message = args.join(' ');
|
|
575
|
+
if (typeof message === 'string' && (
|
|
576
|
+
message.includes('Failed to load resource') ||
|
|
577
|
+
message.includes('is not defined') ||
|
|
578
|
+
message.includes('is not a function')
|
|
579
|
+
)) {
|
|
580
|
+
if (debugEnabled) console.log(`[fingerprint] Suppressed error: ${message}`);
|
|
1656
581
|
return;
|
|
1657
582
|
}
|
|
1658
|
-
|
|
1659
|
-
// Call original handler for non-network rejections
|
|
1660
|
-
if (originalUnhandledRejection) {
|
|
1661
|
-
return originalUnhandledRejection.apply(this, arguments);
|
|
1662
|
-
}
|
|
583
|
+
return originalConsoleError.apply(this, arguments);
|
|
1663
584
|
};
|
|
1664
|
-
|
|
1665
|
-
}, 'network error handling');
|
|
1666
|
-
|
|
585
|
+
}, 'console error suppression');
|
|
1667
586
|
|
|
1668
587
|
}, ua, forceDebug);
|
|
1669
588
|
} catch (stealthErr) {
|
|
1670
|
-
console.warn(`[
|
|
589
|
+
console.warn(`[stealth protection failed] ${currentUrl}: ${stealthErr.message}`);
|
|
1671
590
|
}
|
|
1672
591
|
}
|
|
1673
592
|
}
|
|
1674
593
|
|
|
1675
594
|
/**
|
|
1676
|
-
* Enhanced Brave browser spoofing
|
|
1677
|
-
* Compatible with Puppeteer 23.x
|
|
1678
|
-
* @param {import('puppeteer').Page} page - The Puppeteer page instance
|
|
1679
|
-
* @param {object} siteConfig - The site configuration object
|
|
1680
|
-
* @param {boolean} forceDebug - Whether debug logging is enabled
|
|
1681
|
-
* @param {string} currentUrl - The current URL being processed (for logging)
|
|
1682
|
-
* @returns {Promise<void>}
|
|
595
|
+
* Enhanced Brave browser spoofing
|
|
1683
596
|
*/
|
|
1684
597
|
async function applyBraveSpoofing(page, siteConfig, forceDebug, currentUrl) {
|
|
1685
598
|
if (!siteConfig.isBrave) return;
|
|
1686
599
|
|
|
1687
|
-
if (forceDebug) console.log(`[debug]
|
|
600
|
+
if (forceDebug) console.log(`[debug] Brave spoofing enabled for ${currentUrl}`);
|
|
1688
601
|
|
|
1689
602
|
await page.evaluateOnNewDocument((debugEnabled) => {
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
603
|
+
try {
|
|
604
|
+
Object.defineProperty(navigator, 'brave', {
|
|
605
|
+
get: () => ({
|
|
606
|
+
isBrave: () => Promise.resolve(true),
|
|
607
|
+
setBadge: () => {},
|
|
608
|
+
clearBadge: () => {},
|
|
609
|
+
getAdBlockEnabled: () => Promise.resolve(true),
|
|
610
|
+
getShieldsEnabled: () => Promise.resolve(true)
|
|
611
|
+
}),
|
|
612
|
+
configurable: true
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
if (navigator.userAgent && !navigator.userAgent.includes('Brave')) {
|
|
616
|
+
Object.defineProperty(navigator, 'userAgent', {
|
|
617
|
+
get: () => navigator.userAgent.replace('Chrome/', 'Brave/').replace('Safari/537.36', 'Safari/537.36 Brave/1.60'),
|
|
1704
618
|
configurable: true
|
|
1705
|
-
};
|
|
1706
|
-
|
|
1707
|
-
Object.defineProperty(target, property, safeDescriptor);
|
|
1708
|
-
return true;
|
|
1709
|
-
} catch (defineErr) {
|
|
1710
|
-
if (debugEnabled) {
|
|
1711
|
-
console.log(`[fingerprint] Property definition failed for ${property}: ${defineErr.message}`);
|
|
1712
|
-
}
|
|
1713
|
-
return false;
|
|
619
|
+
});
|
|
1714
620
|
}
|
|
1715
|
-
}
|
|
1716
|
-
|
|
1717
|
-
// More comprehensive Brave spoofing with safe property handling
|
|
1718
|
-
safeDefinePropertyLocal(navigator, 'brave', {
|
|
1719
|
-
get: () => ({
|
|
1720
|
-
isBrave: () => Promise.resolve(true),
|
|
1721
|
-
setBadge: () => {},
|
|
1722
|
-
clearBadge: () => {},
|
|
1723
|
-
getAdBlockEnabled: () => Promise.resolve(true),
|
|
1724
|
-
getShieldsEnabled: () => Promise.resolve(true)
|
|
1725
|
-
})
|
|
1726
|
-
});
|
|
1727
|
-
|
|
1728
|
-
// Brave-specific user agent adjustments with safe handling
|
|
1729
|
-
if (navigator.userAgent && !navigator.userAgent.includes('Brave')) {
|
|
1730
|
-
safeDefinePropertyLocal(navigator, 'userAgent', {
|
|
1731
|
-
get: () => navigator.userAgent.replace('Chrome/', 'Brave/').replace('Safari/537.36', 'Safari/537.36 Brave/1.60')
|
|
1732
|
-
});
|
|
621
|
+
} catch (err) {
|
|
622
|
+
if (debugEnabled) console.log(`[fingerprint] Brave spoofing error: ${err.message}`);
|
|
1733
623
|
}
|
|
1734
624
|
}, forceDebug);
|
|
1735
625
|
}
|
|
1736
626
|
|
|
1737
627
|
/**
|
|
1738
|
-
* Enhanced fingerprint protection with
|
|
1739
|
-
* Compatible with Puppeteer 23.x
|
|
1740
|
-
* @param {import('puppeteer').Page} page - The Puppeteer page instance
|
|
1741
|
-
* @param {object} siteConfig - The site configuration object
|
|
1742
|
-
* @param {boolean} forceDebug - Whether debug logging is enabled
|
|
1743
|
-
* @param {string} currentUrl - The current URL being processed (for logging)
|
|
1744
|
-
* @returns {Promise<void>}
|
|
628
|
+
* Enhanced fingerprint protection with realistic spoofing
|
|
1745
629
|
*/
|
|
1746
630
|
async function applyFingerprintProtection(page, siteConfig, forceDebug, currentUrl) {
|
|
1747
631
|
const fingerprintSetting = siteConfig.fingerprint_protection;
|
|
1748
632
|
if (!fingerprintSetting) return;
|
|
1749
633
|
|
|
1750
|
-
if (forceDebug) console.log(`[debug]
|
|
634
|
+
if (forceDebug) console.log(`[debug] Fingerprint protection enabled for ${currentUrl}`);
|
|
1751
635
|
|
|
1752
636
|
const spoof = fingerprintSetting === 'random' ? getRandomFingerprint() : {
|
|
1753
637
|
deviceMemory: 8,
|
|
@@ -1763,254 +647,93 @@ async function applyFingerprintProtection(page, siteConfig, forceDebug, currentU
|
|
|
1763
647
|
try {
|
|
1764
648
|
await page.evaluateOnNewDocument(({ spoof, debugEnabled }) => {
|
|
1765
649
|
|
|
1766
|
-
// Use local versions of helper functions for this context
|
|
1767
650
|
function safeDefinePropertyLocal(target, property, descriptor) {
|
|
1768
651
|
try {
|
|
1769
|
-
const
|
|
1770
|
-
|
|
1771
|
-
if (existingDescriptor && existingDescriptor.configurable === false) {
|
|
1772
|
-
if (debugEnabled) {
|
|
1773
|
-
console.log(`[fingerprint] Cannot redefine non-configurable property: ${property}`);
|
|
1774
|
-
}
|
|
1775
|
-
return false;
|
|
1776
|
-
}
|
|
652
|
+
const existing = Object.getOwnPropertyDescriptor(target, property);
|
|
653
|
+
if (existing?.configurable === false) return false;
|
|
1777
654
|
|
|
1778
|
-
|
|
655
|
+
Object.defineProperty(target, property, {
|
|
1779
656
|
...descriptor,
|
|
1780
|
-
configurable: true
|
|
1781
|
-
|
|
1782
|
-
};
|
|
1783
|
-
|
|
1784
|
-
Object.defineProperty(target, property, safeDescriptor);
|
|
1785
|
-
return true;
|
|
1786
|
-
} catch (defineErr) {
|
|
1787
|
-
if (debugEnabled) {
|
|
1788
|
-
console.log(`[fingerprint] Property definition failed for ${property}: ${defineErr.message}`);
|
|
1789
|
-
}
|
|
1790
|
-
return false;
|
|
1791
|
-
}
|
|
1792
|
-
}
|
|
1793
|
-
|
|
1794
|
-
function safeExecuteSpoofingLocal(spoofFunction, description) {
|
|
1795
|
-
try {
|
|
1796
|
-
spoofFunction();
|
|
657
|
+
configurable: true
|
|
658
|
+
});
|
|
1797
659
|
return true;
|
|
1798
|
-
} catch (
|
|
1799
|
-
if (debugEnabled) {
|
|
1800
|
-
console.log(`[fingerprint] ${description} failed: ${spoofErr.message}`);
|
|
1801
|
-
}
|
|
660
|
+
} catch (err) {
|
|
661
|
+
if (debugEnabled) console.log(`[fingerprint] Failed to define ${property}: ${err.message}`);
|
|
1802
662
|
return false;
|
|
1803
663
|
}
|
|
1804
664
|
}
|
|
1805
|
-
|
|
1806
|
-
// Enhanced property spoofing with more realistic values and safe handling
|
|
1807
|
-
safeExecuteSpoofingLocal(() => {
|
|
1808
|
-
safeDefinePropertyLocal(navigator, 'platform', {
|
|
1809
|
-
get: () => spoof.platform
|
|
1810
|
-
});
|
|
1811
|
-
}, 'platform spoofing');
|
|
1812
|
-
|
|
1813
|
-
safeExecuteSpoofingLocal(() => {
|
|
1814
|
-
safeDefinePropertyLocal(navigator, 'deviceMemory', {
|
|
1815
|
-
get: () => spoof.deviceMemory
|
|
1816
|
-
});
|
|
1817
|
-
}, 'deviceMemory spoofing');
|
|
1818
|
-
|
|
1819
|
-
safeExecuteSpoofingLocal(() => {
|
|
1820
|
-
safeDefinePropertyLocal(navigator, 'hardwareConcurrency', {
|
|
1821
|
-
get: () => spoof.hardwareConcurrency
|
|
1822
|
-
});
|
|
1823
|
-
}, 'hardwareConcurrency spoofing');
|
|
1824
|
-
|
|
1825
|
-
// Enhanced screen properties with safe handling
|
|
1826
|
-
safeExecuteSpoofingLocal(() => {
|
|
1827
|
-
['width', 'height', 'availWidth', 'availHeight', 'colorDepth', 'pixelDepth'].forEach(prop => {
|
|
1828
|
-
if (spoof.screen[prop] !== undefined) {
|
|
1829
|
-
safeDefinePropertyLocal(window.screen, prop, {
|
|
1830
|
-
get: () => spoof.screen[prop]
|
|
1831
|
-
});
|
|
1832
|
-
}
|
|
1833
|
-
});
|
|
1834
|
-
}, 'screen properties spoofing');
|
|
1835
|
-
|
|
1836
|
-
// Enhanced language spoofing in fingerprint protection
|
|
1837
|
-
safeExecuteSpoofingLocal(() => {
|
|
1838
|
-
const languages = Array.isArray(spoof.language) ? spoof.language : [spoof.language, spoof.language.split('-')[0]];
|
|
1839
|
-
|
|
1840
|
-
safeDefinePropertyLocal(navigator, 'languages', {
|
|
1841
|
-
get: () => languages
|
|
1842
|
-
});
|
|
1843
|
-
|
|
1844
|
-
safeDefinePropertyLocal(navigator, 'language', {
|
|
1845
|
-
get: () => languages[0]
|
|
1846
|
-
});
|
|
1847
|
-
}, 'language spoofing in fingerprint protection');
|
|
1848
665
|
|
|
666
|
+
// Platform spoofing
|
|
667
|
+
safeDefinePropertyLocal(navigator, 'platform', { get: () => spoof.platform });
|
|
1849
668
|
|
|
1850
|
-
//
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
}
|
|
1859
|
-
|
|
1860
|
-
// Check if Intl.DateTimeFormat is available and configurable
|
|
1861
|
-
try {
|
|
1862
|
-
const intlDescriptor = Object.getOwnPropertyDescriptor(window, 'Intl');
|
|
1863
|
-
if (intlDescriptor && intlDescriptor.configurable === false) {
|
|
1864
|
-
if (debugEnabled) {
|
|
1865
|
-
console.log('[fingerprint] Intl object is non-configurable, skipping timezone spoofing');
|
|
1866
|
-
}
|
|
1867
|
-
return;
|
|
1868
|
-
}
|
|
1869
|
-
} catch (intlCheckErr) {
|
|
1870
|
-
if (debugEnabled) {
|
|
1871
|
-
console.log('[fingerprint] Cannot check Intl configurability, skipping timezone spoofing');
|
|
1872
|
-
}
|
|
1873
|
-
return;
|
|
669
|
+
// Memory spoofing
|
|
670
|
+
safeDefinePropertyLocal(navigator, 'deviceMemory', { get: () => spoof.deviceMemory });
|
|
671
|
+
safeDefinePropertyLocal(navigator, 'hardwareConcurrency', { get: () => spoof.hardwareConcurrency });
|
|
672
|
+
|
|
673
|
+
// Screen properties spoofing
|
|
674
|
+
['width', 'height', 'availWidth', 'availHeight', 'colorDepth', 'pixelDepth'].forEach(prop => {
|
|
675
|
+
if (spoof.screen[prop] !== undefined) {
|
|
676
|
+
safeDefinePropertyLocal(window.screen, prop, { get: () => spoof.screen[prop] });
|
|
1874
677
|
}
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
// Validate timezone before setting
|
|
1889
|
-
if (spoof.timezone && typeof spoof.timezone === 'string') {
|
|
1890
|
-
options.timeZone = spoof.timezone;
|
|
1891
|
-
}
|
|
1892
|
-
return options;
|
|
1893
|
-
} catch (optionsErr) {
|
|
1894
|
-
if (debugEnabled) {
|
|
1895
|
-
console.log('[fingerprint] resolvedOptions error, using original:', optionsErr.message);
|
|
1896
|
-
}
|
|
1897
|
-
return originalResolvedOptions.call(this);
|
|
1898
|
-
}
|
|
1899
|
-
};
|
|
1900
|
-
return instance;
|
|
1901
|
-
} catch (instanceErr) {
|
|
1902
|
-
if (debugEnabled) {
|
|
1903
|
-
console.log('[fingerprint] DateTimeFormat instance error, using original:', instanceErr.message);
|
|
1904
|
-
}
|
|
1905
|
-
return new originalDateTimeFormat(...args);
|
|
1906
|
-
}
|
|
1907
|
-
};
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
// Language spoofing
|
|
681
|
+
const languages = Array.isArray(spoof.language) ? spoof.language : [spoof.language, spoof.language.split('-')[0]];
|
|
682
|
+
safeDefinePropertyLocal(navigator, 'languages', { get: () => languages });
|
|
683
|
+
safeDefinePropertyLocal(navigator, 'language', { get: () => languages[0] });
|
|
684
|
+
|
|
685
|
+
// Timezone spoofing
|
|
686
|
+
if (spoof.timezone && window.Intl?.DateTimeFormat) {
|
|
687
|
+
const OriginalDateTimeFormat = window.Intl.DateTimeFormat;
|
|
688
|
+
window.Intl.DateTimeFormat = function(...args) {
|
|
689
|
+
const instance = new OriginalDateTimeFormat(...args);
|
|
690
|
+
const originalResolvedOptions = instance.resolvedOptions;
|
|
1908
691
|
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
}
|
|
692
|
+
instance.resolvedOptions = function() {
|
|
693
|
+
const opts = originalResolvedOptions.call(this);
|
|
694
|
+
opts.timeZone = spoof.timezone;
|
|
695
|
+
return opts;
|
|
696
|
+
};
|
|
697
|
+
return instance;
|
|
698
|
+
};
|
|
699
|
+
Object.setPrototypeOf(window.Intl.DateTimeFormat, OriginalDateTimeFormat);
|
|
700
|
+
|
|
701
|
+
// Timezone offset spoofing
|
|
702
|
+
const timezoneOffsets = {
|
|
703
|
+
'America/New_York': 300,
|
|
704
|
+
'America/Los_Angeles': 480,
|
|
705
|
+
'Europe/London': 0,
|
|
706
|
+
'America/Chicago': 360
|
|
707
|
+
};
|
|
1925
708
|
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
const tzDescriptor = Object.getOwnPropertyDescriptor(Date.prototype, 'getTimezoneOffset');
|
|
1932
|
-
if (tzDescriptor && tzDescriptor.configurable === false) {
|
|
1933
|
-
if (debugEnabled) {
|
|
1934
|
-
console.log('[fingerprint] getTimezoneOffset is non-configurable, skipping');
|
|
1935
|
-
}
|
|
1936
|
-
} else {
|
|
1937
|
-
Date.prototype.getTimezoneOffset = function() {
|
|
1938
|
-
try {
|
|
1939
|
-
// Validate timezone and return appropriate offset
|
|
1940
|
-
const timezoneOffsets = {
|
|
1941
|
-
'America/New_York': 300, // EST offset
|
|
1942
|
-
'America/Los_Angeles': 480, // PST offset
|
|
1943
|
-
'Europe/London': 0, // GMT offset
|
|
1944
|
-
'America/Chicago': 360 // CST offset
|
|
1945
|
-
};
|
|
1946
|
-
|
|
1947
|
-
if (spoof.timezone && timezoneOffsets.hasOwnProperty(spoof.timezone)) {
|
|
1948
|
-
return timezoneOffsets[spoof.timezone];
|
|
1949
|
-
}
|
|
1950
|
-
|
|
1951
|
-
// Fallback to original if timezone not recognized
|
|
1952
|
-
return originalGetTimezoneOffset.call(this);
|
|
1953
|
-
} catch (tzOffsetErr) {
|
|
1954
|
-
if (debugEnabled) {
|
|
1955
|
-
console.log('[fingerprint] getTimezoneOffset error, using original:', tzOffsetErr.message);
|
|
1956
|
-
}
|
|
1957
|
-
return originalGetTimezoneOffset.call(this);
|
|
1958
|
-
}
|
|
1959
|
-
};
|
|
1960
|
-
}
|
|
1961
|
-
} catch (timezoneOffsetErr) {
|
|
1962
|
-
if (debugEnabled) {
|
|
1963
|
-
console.log('[fingerprint] Timezone offset spoofing failed:', timezoneOffsetErr.message);
|
|
1964
|
-
}
|
|
1965
|
-
}
|
|
1966
|
-
}, 'timezone spoofing');
|
|
709
|
+
const originalGetTimezoneOffset = Date.prototype.getTimezoneOffset;
|
|
710
|
+
Date.prototype.getTimezoneOffset = function() {
|
|
711
|
+
return timezoneOffsets[spoof.timezone] || originalGetTimezoneOffset.call(this);
|
|
712
|
+
};
|
|
713
|
+
}
|
|
1967
714
|
|
|
1968
|
-
//
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
if (spoof.doNotTrack !== undefined) {
|
|
1977
|
-
safeDefinePropertyLocal(navigator, 'doNotTrack', {
|
|
1978
|
-
get: () => spoof.doNotTrack
|
|
1979
|
-
});
|
|
1980
|
-
}
|
|
1981
|
-
}, 'cookie/DNT spoofing');
|
|
715
|
+
// Cookie and DNT spoofing
|
|
716
|
+
if (spoof.cookieEnabled !== undefined) {
|
|
717
|
+
safeDefinePropertyLocal(navigator, 'cookieEnabled', { get: () => spoof.cookieEnabled });
|
|
718
|
+
}
|
|
719
|
+
if (spoof.doNotTrack !== undefined) {
|
|
720
|
+
safeDefinePropertyLocal(navigator, 'doNotTrack', { get: () => spoof.doNotTrack });
|
|
721
|
+
}
|
|
1982
722
|
|
|
1983
723
|
}, { spoof, debugEnabled: forceDebug });
|
|
1984
724
|
} catch (err) {
|
|
1985
|
-
console.warn(`[
|
|
725
|
+
console.warn(`[fingerprint protection failed] ${currentUrl}: ${err.message}`);
|
|
1986
726
|
}
|
|
1987
727
|
}
|
|
1988
728
|
|
|
1989
729
|
/**
|
|
1990
|
-
*
|
|
1991
|
-
* Compatible with Puppeteer 23.x
|
|
1992
|
-
* @param {import('puppeteer').Page} page - The Puppeteer page instance
|
|
1993
|
-
* @param {boolean} forceDebug - Whether debug logging is enabled
|
|
1994
|
-
* @returns {Promise<void>}
|
|
730
|
+
* Simulate human-like behavior
|
|
1995
731
|
*/
|
|
1996
732
|
async function simulateHumanBehavior(page, forceDebug) {
|
|
1997
733
|
try {
|
|
1998
734
|
await page.evaluateOnNewDocument((debugEnabled) => {
|
|
1999
735
|
|
|
2000
|
-
|
|
2001
|
-
try {
|
|
2002
|
-
spoofFunction();
|
|
2003
|
-
return true;
|
|
2004
|
-
} catch (spoofErr) {
|
|
2005
|
-
if (debugEnabled) {
|
|
2006
|
-
console.log(`[fingerprint] ${description} failed: ${spoofErr.message}`);
|
|
2007
|
-
}
|
|
2008
|
-
return false;
|
|
2009
|
-
}
|
|
2010
|
-
}
|
|
2011
|
-
|
|
2012
|
-
// Simulate human-like mouse movements with safe handling
|
|
2013
|
-
safeExecuteSpoofingLocal(() => {
|
|
736
|
+
try {
|
|
2014
737
|
let mouseX = Math.random() * window.innerWidth;
|
|
2015
738
|
let mouseY = Math.random() * window.innerHeight;
|
|
2016
739
|
|
|
@@ -2027,74 +750,41 @@ async function simulateHumanBehavior(page, forceDebug) {
|
|
|
2027
750
|
clientY: mouseY,
|
|
2028
751
|
bubbles: true
|
|
2029
752
|
}));
|
|
2030
|
-
} catch (
|
|
2031
|
-
// Ignore mouse event errors
|
|
2032
|
-
}
|
|
753
|
+
} catch (e) {}
|
|
2033
754
|
}, 1000 + Math.random() * 2000);
|
|
2034
755
|
|
|
2035
|
-
//
|
|
2036
|
-
setTimeout(() => {
|
|
2037
|
-
try {
|
|
2038
|
-
if (Math.random() > 0.7) {
|
|
2039
|
-
document.dispatchEvent(new MouseEvent('click', {
|
|
2040
|
-
clientX: mouseX,
|
|
2041
|
-
clientY: mouseY,
|
|
2042
|
-
bubbles: true
|
|
2043
|
-
}));
|
|
2044
|
-
}
|
|
2045
|
-
|
|
2046
|
-
// Simulate scroll events
|
|
2047
|
-
if (Math.random() > 0.8) {
|
|
2048
|
-
window.scrollBy(0, Math.random() * 100 - 50);
|
|
2049
|
-
}
|
|
2050
|
-
} catch (interactionErr) {
|
|
2051
|
-
// Ignore interaction errors
|
|
2052
|
-
}
|
|
2053
|
-
}, 5000 + Math.random() * 10000);
|
|
2054
|
-
|
|
2055
|
-
// Stop simulation after 30 seconds to avoid detection
|
|
756
|
+
// Stop after 30 seconds
|
|
2056
757
|
setTimeout(() => {
|
|
2057
|
-
try {
|
|
2058
|
-
clearInterval(moveInterval);
|
|
2059
|
-
} catch (clearErr) {
|
|
2060
|
-
// Ignore clear errors
|
|
2061
|
-
}
|
|
758
|
+
try { clearInterval(moveInterval); } catch (e) {}
|
|
2062
759
|
}, 30000);
|
|
2063
|
-
|
|
760
|
+
|
|
761
|
+
} catch (err) {
|
|
762
|
+
if (debugEnabled) console.log(`[fingerprint] Human behavior simulation failed: ${err.message}`);
|
|
763
|
+
}
|
|
2064
764
|
|
|
2065
765
|
}, forceDebug);
|
|
2066
766
|
} catch (err) {
|
|
2067
|
-
if (forceDebug) console.log(`[debug] Human behavior simulation failed: ${err.message}`);
|
|
767
|
+
if (forceDebug) console.log(`[debug] Human behavior simulation setup failed: ${err.message}`);
|
|
2068
768
|
}
|
|
2069
769
|
}
|
|
2070
770
|
|
|
2071
771
|
/**
|
|
2072
|
-
*
|
|
2073
|
-
* Compatible with Puppeteer 23.x
|
|
2074
|
-
* @param {import('puppeteer').Page} page - The Puppeteer page instance
|
|
2075
|
-
* @param {object} siteConfig - The site configuration object
|
|
2076
|
-
* @param {boolean} forceDebug - Whether debug logging is enabled
|
|
2077
|
-
* @param {string} currentUrl - The current URL being processed (for logging)
|
|
2078
|
-
* @returns {Promise<void>}
|
|
772
|
+
* Main function that applies all fingerprint spoofing techniques
|
|
2079
773
|
*/
|
|
2080
774
|
async function applyAllFingerprintSpoofing(page, siteConfig, forceDebug, currentUrl) {
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
try {
|
|
2095
|
-
await applyFingerprintProtection(page, siteConfig, forceDebug, currentUrl);
|
|
2096
|
-
} catch (fpErr) {
|
|
2097
|
-
if (forceDebug) console.log(`[debug] Fingerprint protection failed for ${currentUrl}: ${fpErr.message}`);
|
|
775
|
+
|
|
776
|
+
const techniques = [
|
|
777
|
+
{ fn: applyUserAgentSpoofing, name: 'User agent spoofing' },
|
|
778
|
+
{ fn: applyBraveSpoofing, name: 'Brave spoofing' },
|
|
779
|
+
{ fn: applyFingerprintProtection, name: 'Fingerprint protection' }
|
|
780
|
+
];
|
|
781
|
+
|
|
782
|
+
for (const { fn, name } of techniques) {
|
|
783
|
+
try {
|
|
784
|
+
await fn(page, siteConfig, forceDebug, currentUrl);
|
|
785
|
+
} catch (err) {
|
|
786
|
+
if (forceDebug) console.log(`[debug] ${name} failed for ${currentUrl}: ${err.message}`);
|
|
787
|
+
}
|
|
2098
788
|
}
|
|
2099
789
|
|
|
2100
790
|
// Add human behavior simulation if user agent spoofing is enabled
|
|
@@ -2107,6 +797,12 @@ async function applyAllFingerprintSpoofing(page, siteConfig, forceDebug, current
|
|
|
2107
797
|
}
|
|
2108
798
|
}
|
|
2109
799
|
|
|
800
|
+
// Legacy compatibility function - maintained for backwards compatibility
|
|
801
|
+
function safeExecuteSpoofing(spoofFunction, description, forceDebug = false) {
|
|
802
|
+
return safeSpoofingExecution(spoofFunction, description, { debug: forceDebug });
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
|
|
2110
806
|
module.exports = {
|
|
2111
807
|
getRandomFingerprint,
|
|
2112
808
|
getRealisticScreenResolution,
|
|
@@ -2116,7 +812,10 @@ module.exports = {
|
|
|
2116
812
|
applyAllFingerprintSpoofing,
|
|
2117
813
|
simulateHumanBehavior,
|
|
2118
814
|
safeDefineProperty,
|
|
2119
|
-
safeExecuteSpoofing,
|
|
815
|
+
safeExecuteSpoofing, // Legacy compatibility
|
|
816
|
+
safeSpoofingExecution,
|
|
817
|
+
createFingerprintMocks,
|
|
818
|
+
applyTimezoneSpoofing,
|
|
2120
819
|
DEFAULT_PLATFORM,
|
|
2121
820
|
DEFAULT_TIMEZONE
|
|
2122
821
|
};
|