1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
// Stealth JavaScript for Anti-Bot Detection Bypass
// This code is injected into every page to hide automation and randomize fingerprints
// Based on: Firecrawl stealth proxy analysis + puppeteer-extra-plugin-stealth
//
// Techniques implemented:
// 1. Remove navigator.webdriver
// 2. Add window.chrome object
// 3. Spoof Permissions API
// 4. Randomize plugins
// 5. Override languages
// 6. Fix hairline detector
// 7. Canvas fingerprint randomization
// 8. WebGL vendor/renderer spoofing
// 9. Media devices enumeration
// 10. Battery API spoofing
(function() {
'use strict';
// ============================================================================
// 1. Remove navigator.webdriver (most common detection)
// ============================================================================
// Delete from prototype first
if (Object.getPrototypeOf(navigator).hasOwnProperty('webdriver')) {
delete Object.getPrototypeOf(navigator).webdriver;
}
// Then redefine as undefined
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined,
configurable: true
});
// ============================================================================
// 2. Add window.chrome object (avoid headless detection)
// ============================================================================
if (!window.chrome) {
Object.defineProperty(window, 'chrome', {
get: () => ({
runtime: {},
loadTimes: function() {},
csi: function() {},
app: {}
}),
configurable: true
});
}
// ============================================================================
// 3. Spoof Permissions API (notifications permission)
// ============================================================================
if (window.navigator && window.navigator.permissions) {
const originalQuery = window.navigator.permissions.query;
window.navigator.permissions.query = (parameters) => (
parameters.name === 'notifications' ?
Promise.resolve({ state: Notification.permission }) :
originalQuery(parameters)
);
}
// ============================================================================
// 4. Randomize plugins (empty plugins array = headless)
// ============================================================================
Object.defineProperty(navigator, 'plugins', {
get: () => {
// Return realistic plugin list
return [
{
name: 'Chrome PDF Plugin',
filename: 'internal-pdf-viewer',
description: 'Portable Document Format'
},
{
name: 'Chrome PDF Viewer',
filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai',
description: 'Portable Document Format'
},
{
name: 'Native Client',
filename: 'internal-nacl-plugin',
description: 'Native Client Executable'
}
];
},
configurable: true
});
// ============================================================================
// 5. Override languages
// ============================================================================
Object.defineProperty(navigator, 'languages', {
get: () => ['en-US', 'en'],
configurable: true
});
// ============================================================================
// 6. Fix hairline detector (outerWidth/outerHeight)
// ============================================================================
// In headless browsers, outerWidth === innerWidth (no browser UI)
// Real browsers have UI, so outerWidth === innerWidth and outerHeight > innerHeight
Object.defineProperty(window, 'outerWidth', {
get: () => window.innerWidth,
configurable: true
});
Object.defineProperty(window, 'outerHeight', {
get: () => window.innerHeight + 74, // Chrome UI is ~74px
configurable: true
});
// ============================================================================
// 7. Canvas fingerprint randomization
// ============================================================================
// Add minimal noise to canvas to prevent fingerprinting
const originalToDataURL = HTMLCanvasElement.prototype.toDataURL;
const originalToBlob = HTMLCanvasElement.prototype.toBlob;
const originalGetImageData = CanvasRenderingContext2D.prototype.getImageData;
// Helper: Add noise to image data
const addCanvasNoise = function(imageData) {
// Add minimal random noise (±1-5 per channel)
const noise = () => Math.floor(Math.random() * 10) - 5;
// Only modify a few pixels to avoid visual detection
const pixelsToModify = Math.min(10, imageData.data.length / 4);
for (let i = 0; i < pixelsToModify; i++) {
const randomIndex = Math.floor(Math.random() * (imageData.data.length / 4)) * 4;
// Modify RGB (not alpha)
imageData.data[randomIndex] = Math.min(255, Math.max(0, imageData.data[randomIndex] + noise()));
imageData.data[randomIndex + 1] = Math.min(255, Math.max(0, imageData.data[randomIndex + 1] + noise()));
imageData.data[randomIndex + 2] = Math.min(255, Math.max(0, imageData.data[randomIndex + 2] + noise()));
}
return imageData;
};
// Override toDataURL
HTMLCanvasElement.prototype.toDataURL = function() {
try {
const context = this.getContext('2d');
if (context && this.width > 0 && this.height > 0) {
const imageData = context.getImageData(0, 0, this.width, this.height);
const noisyData = addCanvasNoise(imageData);
context.putImageData(noisyData, 0, 0);
}
} catch (e) {
// Silently fail if canvas is tainted
}
return originalToDataURL.apply(this, arguments);
};
// Override getImageData
CanvasRenderingContext2D.prototype.getImageData = function() {
const imageData = originalGetImageData.apply(this, arguments);
return addCanvasNoise(imageData);
};
// ============================================================================
// 8. WebGL vendor/renderer spoofing
// ============================================================================
// Headless browsers often expose different WebGL info
const getParameter = WebGLRenderingContext.prototype.getParameter;
WebGLRenderingContext.prototype.getParameter = function(parameter) {
// 37445 = UNMASKED_VENDOR_WEBGL
if (parameter === 37445) {
return 'Intel Inc.';
}
// 37446 = UNMASKED_RENDERER_WEBGL
if (parameter === 37446) {
return 'Intel Iris OpenGL Engine';
}
return getParameter.apply(this, arguments);
};
// Also override for WebGL2
if (typeof WebGL2RenderingContext !== 'undefined') {
const getParameter2 = WebGL2RenderingContext.prototype.getParameter;
WebGL2RenderingContext.prototype.getParameter = function(parameter) {
if (parameter === 37445) {
return 'Intel Inc.';
}
if (parameter === 37446) {
return 'Intel Iris OpenGL Engine';
}
return getParameter2.apply(this, arguments);
};
}
// ============================================================================
// 9. Media devices enumeration
// ============================================================================
// Headless browsers often have no media devices
if (navigator.mediaDevices && navigator.mediaDevices.enumerateDevices) {
navigator.mediaDevices.enumerateDevices = () => Promise.resolve([
{
deviceId: 'default',
kind: 'audioinput',
label: 'Default - Microphone',
groupId: 'default-audio-group'
},
{
deviceId: 'default',
kind: 'audiooutput',
label: 'Default - Speaker',
groupId: 'default-audio-group'
},
{
deviceId: 'default',
kind: 'videoinput',
label: 'Default - Camera',
groupId: 'default-video-group'
}
]);
}
// ============================================================================
// 10. Battery API spoofing
// ============================================================================
// Headless browsers often have no battery or unrealistic values
if (navigator.getBattery) {
const originalGetBattery = navigator.getBattery;
navigator.getBattery = () => Promise.resolve({
charging: true,
chargingTime: 0,
dischargingTime: Infinity,
level: 1.0,
addEventListener: function() {},
removeEventListener: function() {},
dispatchEvent: function() { return true; }
});
}
// ============================================================================
// Additional Stealth: Conceal CDP Runtime
// ============================================================================
// Remove CDP runtime detection (Chromium DevTools Protocol)
if (window.cdc_adoQpoasnfa76pfcZLmcfl_Array ||
window.cdc_adoQpoasnfa76pfcZLmcfl_Promise ||
window.cdc_adoQpoasnfa76pfcZLmcfl_Symbol) {
delete window.cdc_adoQpoasnfa76pfcZLmcfl_Array;
delete window.cdc_adoQpoasnfa76pfcZLmcfl_Promise;
delete window.cdc_adoQpoasnfa76pfcZLmcfl_Symbol;
}
// ============================================================================
// Stealth Injection Complete
// ============================================================================
// Optional: Log success (for debugging)
// console.log('[Stealth] Anti-detection techniques applied successfully');
})();