@niksbanna/bot-detector 1.0.2 → 1.0.4
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/README.md +22 -0
- package/dist/bot-detector.cjs.js +115 -75
- package/dist/bot-detector.cjs.js.map +2 -2
- package/dist/bot-detector.esm.js +115 -75
- package/dist/bot-detector.esm.js.map +2 -2
- package/dist/bot-detector.iife.js +115 -75
- package/dist/bot-detector.iife.js.map +2 -2
- package/dist/bot-detector.iife.min.js +3 -1
- package/package.json +1 -2
- package/src/core/BotDetector.js +0 -284
- package/src/core/ScoringEngine.js +0 -134
- package/src/core/Signal.js +0 -181
- package/src/core/VerdictEngine.js +0 -132
- package/src/index.js +0 -273
- package/src/signals/automation/PhantomJSSignal.js +0 -137
- package/src/signals/automation/PlaywrightSignal.js +0 -129
- package/src/signals/automation/PuppeteerSignal.js +0 -122
- package/src/signals/automation/SeleniumSignal.js +0 -151
- package/src/signals/automation/index.js +0 -8
- package/src/signals/behavior/InteractionTimingSignal.js +0 -170
- package/src/signals/behavior/KeyboardPatternSignal.js +0 -235
- package/src/signals/behavior/MouseMovementSignal.js +0 -215
- package/src/signals/behavior/ScrollBehaviorSignal.js +0 -236
- package/src/signals/behavior/index.js +0 -8
- package/src/signals/environment/HeadlessSignal.js +0 -97
- package/src/signals/environment/NavigatorAnomalySignal.js +0 -117
- package/src/signals/environment/PermissionsSignal.js +0 -76
- package/src/signals/environment/WebDriverSignal.js +0 -58
- package/src/signals/environment/index.js +0 -8
- package/src/signals/fingerprint/AudioContextSignal.js +0 -158
- package/src/signals/fingerprint/CanvasSignal.js +0 -133
- package/src/signals/fingerprint/PluginsSignal.js +0 -106
- package/src/signals/fingerprint/ScreenSignal.js +0 -157
- package/src/signals/fingerprint/WebGLSignal.js +0 -146
- package/src/signals/fingerprint/index.js +0 -9
- package/src/signals/timing/DOMContentTimingSignal.js +0 -159
- package/src/signals/timing/PageLoadSignal.js +0 -165
- package/src/signals/timing/index.js +0 -6
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Detects PhantomJS-specific artifacts.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { Signal } from '../../core/Signal.js';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Detects artifacts left by PhantomJS.
|
|
9
|
-
* PhantomJS is an older headless browser that leaves specific traces.
|
|
10
|
-
*/
|
|
11
|
-
class PhantomJSSignal extends Signal {
|
|
12
|
-
static id = 'phantomjs';
|
|
13
|
-
static category = 'automation';
|
|
14
|
-
static weight = 1.0;
|
|
15
|
-
static description = 'Detects PhantomJS automation artifacts';
|
|
16
|
-
|
|
17
|
-
async detect() {
|
|
18
|
-
const indicators = [];
|
|
19
|
-
let confidence = 0;
|
|
20
|
-
|
|
21
|
-
// Check for PhantomJS globals
|
|
22
|
-
if (window.callPhantom) {
|
|
23
|
-
indicators.push('callPhantom');
|
|
24
|
-
confidence = Math.max(confidence, 1.0);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (window._phantom) {
|
|
28
|
-
indicators.push('_phantom');
|
|
29
|
-
confidence = Math.max(confidence, 1.0);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (window.phantom) {
|
|
33
|
-
indicators.push('phantom');
|
|
34
|
-
confidence = Math.max(confidence, 1.0);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Check for PhantomJS in user agent
|
|
38
|
-
const ua = navigator.userAgent || '';
|
|
39
|
-
if (ua.includes('PhantomJS')) {
|
|
40
|
-
indicators.push('phantomjs-ua');
|
|
41
|
-
confidence = Math.max(confidence, 1.0);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Check for PhantomJS specific properties
|
|
45
|
-
if (window.__phantomas) {
|
|
46
|
-
indicators.push('phantomas');
|
|
47
|
-
confidence = Math.max(confidence, 1.0);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Check for CasperJS (built on PhantomJS)
|
|
51
|
-
if (window.__casper) {
|
|
52
|
-
indicators.push('casperjs');
|
|
53
|
-
confidence = Math.max(confidence, 1.0);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (window.casper) {
|
|
57
|
-
indicators.push('casper-global');
|
|
58
|
-
confidence = Math.max(confidence, 1.0);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Check for SlimerJS (PhantomJS alternative)
|
|
62
|
-
if (window.slimer) {
|
|
63
|
-
indicators.push('slimerjs');
|
|
64
|
-
confidence = Math.max(confidence, 1.0);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Check for NightmareJS (Electron-based, similar patterns)
|
|
68
|
-
if (window.__nightmare) {
|
|
69
|
-
indicators.push('nightmare');
|
|
70
|
-
confidence = Math.max(confidence, 1.0);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (window.nightmare) {
|
|
74
|
-
indicators.push('nightmare-global');
|
|
75
|
-
confidence = Math.max(confidence, 1.0);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Check for function sources containing PhantomJS
|
|
79
|
-
try {
|
|
80
|
-
const funcString = Function.prototype.toString.call(Function);
|
|
81
|
-
if (funcString.includes('phantom') || funcString.includes('Phantom')) {
|
|
82
|
-
indicators.push('function-prototype-phantom');
|
|
83
|
-
confidence = Math.max(confidence, 0.8);
|
|
84
|
-
}
|
|
85
|
-
} catch (e) {
|
|
86
|
-
// Ignore errors
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Check for PhantomJS-specific behaviors
|
|
90
|
-
// PhantomJS has a specific way of handling errors
|
|
91
|
-
try {
|
|
92
|
-
throw new Error('test');
|
|
93
|
-
} catch (e) {
|
|
94
|
-
const stack = e.stack || '';
|
|
95
|
-
if (stack.includes('phantom')) {
|
|
96
|
-
indicators.push('stack-trace-phantom');
|
|
97
|
-
confidence = Math.max(confidence, 0.9);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Check for PhantomJS-specific plugin handling
|
|
102
|
-
if (navigator.plugins && navigator.plugins.length === 0) {
|
|
103
|
-
// Combined with other PhantomJS indicators
|
|
104
|
-
if (indicators.length > 0) {
|
|
105
|
-
indicators.push('no-plugins-phantom');
|
|
106
|
-
confidence = Math.max(confidence, 0.5);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Check for specific PhantomJS window properties
|
|
111
|
-
const phantomProps = [
|
|
112
|
-
'__PHANTOM__',
|
|
113
|
-
'PHANTOM',
|
|
114
|
-
'Buffer', // PhantomJS exposes Node.js Buffer
|
|
115
|
-
'process', // May expose Node.js process
|
|
116
|
-
];
|
|
117
|
-
|
|
118
|
-
for (const prop of phantomProps) {
|
|
119
|
-
if (prop in window && prop !== 'Buffer' && prop !== 'process') {
|
|
120
|
-
indicators.push(`phantom-prop-${prop.toLowerCase()}`);
|
|
121
|
-
confidence = Math.max(confidence, 0.9);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Check for QtWebKit (PhantomJS engine)
|
|
126
|
-
if (ua.includes('QtWebKit')) {
|
|
127
|
-
indicators.push('qtwebkit');
|
|
128
|
-
confidence = Math.max(confidence, 0.7);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const triggered = indicators.length > 0;
|
|
132
|
-
|
|
133
|
-
return this.createResult(triggered, { indicators }, confidence);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export { PhantomJSSignal };
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Detects Playwright-specific artifacts.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { Signal } from '../../core/Signal.js';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Detects artifacts left by Playwright automation.
|
|
9
|
-
* Playwright injects specific objects and leaves traces.
|
|
10
|
-
*/
|
|
11
|
-
class PlaywrightSignal extends Signal {
|
|
12
|
-
static id = 'playwright';
|
|
13
|
-
static category = 'automation';
|
|
14
|
-
static weight = 1.0;
|
|
15
|
-
static description = 'Detects Playwright automation artifacts';
|
|
16
|
-
|
|
17
|
-
async detect() {
|
|
18
|
-
const indicators = [];
|
|
19
|
-
let confidence = 0;
|
|
20
|
-
|
|
21
|
-
// Check for Playwright namespace
|
|
22
|
-
if (window.__playwright) {
|
|
23
|
-
indicators.push('playwright-namespace');
|
|
24
|
-
confidence = Math.max(confidence, 1.0);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Check for Playwright-injected objects
|
|
28
|
-
const playwrightGlobals = [
|
|
29
|
-
'__playwright',
|
|
30
|
-
'__pw_manual',
|
|
31
|
-
'__pwInitScripts',
|
|
32
|
-
'playwright',
|
|
33
|
-
];
|
|
34
|
-
|
|
35
|
-
for (const global of playwrightGlobals) {
|
|
36
|
-
if (global in window) {
|
|
37
|
-
indicators.push(`global-${global}`);
|
|
38
|
-
confidence = Math.max(confidence, 1.0);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Check for Playwright's binding pattern
|
|
43
|
-
if (window.__playwright__binding__) {
|
|
44
|
-
indicators.push('playwright-binding');
|
|
45
|
-
confidence = Math.max(confidence, 1.0);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Check for Playwright-specific user agent markers
|
|
49
|
-
const ua = navigator.userAgent || '';
|
|
50
|
-
if (ua.includes('Playwright') || ua.includes('HeadlessChrome')) {
|
|
51
|
-
indicators.push('playwright-ua-marker');
|
|
52
|
-
confidence = Math.max(confidence, ua.includes('Playwright') ? 1.0 : 0.7);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Check for navigator.webdriver (Playwright sets this in headless)
|
|
56
|
-
if (navigator.webdriver === true) {
|
|
57
|
-
indicators.push('webdriver-flag');
|
|
58
|
-
confidence = Math.max(confidence, 0.8);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Check for Playwright's evaluate scope pattern
|
|
62
|
-
try {
|
|
63
|
-
// Playwright injects __pwBinding__ functions
|
|
64
|
-
const windowKeys = Object.keys(window);
|
|
65
|
-
const pwBindings = windowKeys.filter(k => k.startsWith('__pw'));
|
|
66
|
-
|
|
67
|
-
if (pwBindings.length > 0) {
|
|
68
|
-
indicators.push('pw-bindings');
|
|
69
|
-
confidence = Math.max(confidence, 1.0);
|
|
70
|
-
}
|
|
71
|
-
} catch (e) {
|
|
72
|
-
// Ignore errors
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Check for Playwright's typical initialization patterns
|
|
76
|
-
if (typeof window.__pw_date_intercepted !== 'undefined') {
|
|
77
|
-
indicators.push('date-interception');
|
|
78
|
-
confidence = Math.max(confidence, 0.9);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Check for Playwright's geolocation mock
|
|
82
|
-
if (window.__pw_geolocation__) {
|
|
83
|
-
indicators.push('geolocation-mock');
|
|
84
|
-
confidence = Math.max(confidence, 0.9);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Check for Playwright's permission override
|
|
88
|
-
if (window.__pw_permissions__) {
|
|
89
|
-
indicators.push('permissions-override');
|
|
90
|
-
confidence = Math.max(confidence, 0.9);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Check CDP session artifacts
|
|
94
|
-
if (window.__cdpSession__) {
|
|
95
|
-
indicators.push('cdp-session');
|
|
96
|
-
confidence = Math.max(confidence, 0.8);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Check for error stack traces containing Playwright
|
|
100
|
-
try {
|
|
101
|
-
throw new Error('stack trace test');
|
|
102
|
-
} catch (e) {
|
|
103
|
-
const stack = e.stack || '';
|
|
104
|
-
if (stack.includes('playwright') || stack.includes('__pw')) {
|
|
105
|
-
indicators.push('stack-trace-playwright');
|
|
106
|
-
confidence = Math.max(confidence, 0.8);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Check for Playwright's locale/timezone mocking
|
|
111
|
-
try {
|
|
112
|
-
const date = new Date();
|
|
113
|
-
const localeString = date.toLocaleString();
|
|
114
|
-
// Playwright often mocks timezone
|
|
115
|
-
if (window.__pwTimezone__) {
|
|
116
|
-
indicators.push('timezone-mock');
|
|
117
|
-
confidence = Math.max(confidence, 0.8);
|
|
118
|
-
}
|
|
119
|
-
} catch (e) {
|
|
120
|
-
// Ignore errors
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const triggered = indicators.length > 0;
|
|
124
|
-
|
|
125
|
-
return this.createResult(triggered, { indicators }, confidence);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export { PlaywrightSignal };
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Detects Puppeteer-specific artifacts.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { Signal } from '../../core/Signal.js';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Detects artifacts left by Puppeteer automation.
|
|
9
|
-
* Puppeteer leaves various fingerprints in the browser context.
|
|
10
|
-
*/
|
|
11
|
-
class PuppeteerSignal extends Signal {
|
|
12
|
-
static id = 'puppeteer';
|
|
13
|
-
static category = 'automation';
|
|
14
|
-
static weight = 1.0;
|
|
15
|
-
static description = 'Detects Puppeteer automation artifacts';
|
|
16
|
-
|
|
17
|
-
async detect() {
|
|
18
|
-
const indicators = [];
|
|
19
|
-
let confidence = 0;
|
|
20
|
-
|
|
21
|
-
// Check for Puppeteer evaluation script marker
|
|
22
|
-
if (window.__puppeteer_evaluation_script__) {
|
|
23
|
-
indicators.push('puppeteer-evaluation-script');
|
|
24
|
-
confidence = Math.max(confidence, 1.0);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Check for Puppeteer-injected functions
|
|
28
|
-
const puppeteerGlobals = [
|
|
29
|
-
'__puppeteer_evaluation_script__',
|
|
30
|
-
'__puppeteer',
|
|
31
|
-
'puppeteer',
|
|
32
|
-
];
|
|
33
|
-
|
|
34
|
-
for (const global of puppeteerGlobals) {
|
|
35
|
-
if (global in window) {
|
|
36
|
-
indicators.push(`global-${global}`);
|
|
37
|
-
confidence = Math.max(confidence, 1.0);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Check for HeadlessChrome in user agent (common with Puppeteer)
|
|
42
|
-
const ua = navigator.userAgent || '';
|
|
43
|
-
if (ua.includes('HeadlessChrome')) {
|
|
44
|
-
indicators.push('headless-chrome-ua');
|
|
45
|
-
confidence = Math.max(confidence, 0.9);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Check for Puppeteer's typical Chrome DevTools Protocol artifacts
|
|
49
|
-
if (window.cdc_adoQpoasnfa76pfcZLmcfl_Array ||
|
|
50
|
-
window.cdc_adoQpoasnfa76pfcZLmcfl_Promise ||
|
|
51
|
-
window.cdc_adoQpoasnfa76pfcZLmcfl_Symbol) {
|
|
52
|
-
indicators.push('cdp-artifacts');
|
|
53
|
-
confidence = Math.max(confidence, 1.0);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Check for DevTools protocol detection
|
|
57
|
-
try {
|
|
58
|
-
// Puppeteer often leaves eval traces
|
|
59
|
-
const evalTest = window.eval.toString();
|
|
60
|
-
if (evalTest.includes('puppeteer')) {
|
|
61
|
-
indicators.push('eval-puppeteer');
|
|
62
|
-
confidence = Math.max(confidence, 0.9);
|
|
63
|
-
}
|
|
64
|
-
} catch (e) {
|
|
65
|
-
// Ignore errors
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Check for typical Puppeteer page.evaluate patterns in stack traces
|
|
69
|
-
try {
|
|
70
|
-
throw new Error('stack trace test');
|
|
71
|
-
} catch (e) {
|
|
72
|
-
const stack = e.stack || '';
|
|
73
|
-
if (stack.includes('puppeteer') || stack.includes('pptr')) {
|
|
74
|
-
indicators.push('stack-trace-puppeteer');
|
|
75
|
-
confidence = Math.max(confidence, 0.8);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Check for Puppeteer's default viewport (800x600)
|
|
80
|
-
if (window.innerWidth === 800 && window.innerHeight === 600) {
|
|
81
|
-
// Only weak indicator - could be coincidence
|
|
82
|
-
indicators.push('default-viewport');
|
|
83
|
-
confidence = Math.max(confidence, 0.3);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Check for navigator.webdriver (Puppeteer sets this)
|
|
87
|
-
if (navigator.webdriver === true) {
|
|
88
|
-
indicators.push('webdriver-flag');
|
|
89
|
-
confidence = Math.max(confidence, 0.9);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Check for binding injection pattern
|
|
93
|
-
// Puppeteer's exposeFunction creates window bindings
|
|
94
|
-
// Exclude __zone_symbol__* (Angular/Zone.js) which are benign
|
|
95
|
-
const suspiciousBindings = Object.keys(window).filter(key => {
|
|
96
|
-
return key.startsWith('__') &&
|
|
97
|
-
!key.startsWith('__zone_symbol__') &&
|
|
98
|
-
typeof window[key] === 'function';
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
if (suspiciousBindings.length > 5) {
|
|
102
|
-
indicators.push('suspicious-bindings');
|
|
103
|
-
confidence = Math.max(confidence, 0.5);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Check Chrome object anomalies (Puppeteer headless)
|
|
107
|
-
// Note: in a normal Chrome page, window.chrome.runtime exists but runtime.id is
|
|
108
|
-
// only set inside Chrome extensions. Only flag when runtime itself is absent.
|
|
109
|
-
if (typeof window.chrome !== 'undefined') {
|
|
110
|
-
if (!window.chrome.runtime) {
|
|
111
|
-
indicators.push('incomplete-chrome-object');
|
|
112
|
-
confidence = Math.max(confidence, 0.4);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const triggered = indicators.length > 0;
|
|
117
|
-
|
|
118
|
-
return this.createResult(triggered, { indicators }, confidence);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export { PuppeteerSignal };
|
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Detects Selenium WebDriver artifacts.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { Signal } from '../../core/Signal.js';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Detects artifacts left by Selenium WebDriver.
|
|
9
|
-
* Selenium leaves various fingerprints in the browser context.
|
|
10
|
-
*/
|
|
11
|
-
class SeleniumSignal extends Signal {
|
|
12
|
-
static id = 'selenium';
|
|
13
|
-
static category = 'automation';
|
|
14
|
-
static weight = 1.0;
|
|
15
|
-
static description = 'Detects Selenium WebDriver artifacts';
|
|
16
|
-
|
|
17
|
-
async detect() {
|
|
18
|
-
const indicators = [];
|
|
19
|
-
let confidence = 0;
|
|
20
|
-
|
|
21
|
-
// Check for navigator.webdriver (standard WebDriver flag)
|
|
22
|
-
if (navigator.webdriver === true) {
|
|
23
|
-
indicators.push('webdriver-flag');
|
|
24
|
-
confidence = Math.max(confidence, 1.0);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Check for Selenium-specific globals
|
|
28
|
-
const seleniumGlobals = [
|
|
29
|
-
'_selenium',
|
|
30
|
-
'callSelenium',
|
|
31
|
-
'_Selenium_IDE_Recorder',
|
|
32
|
-
'__selenium_evaluate',
|
|
33
|
-
'__selenium_unwrap',
|
|
34
|
-
'__webdriver_evaluate',
|
|
35
|
-
'__webdriver_unwrap',
|
|
36
|
-
'__webdriver_script_function',
|
|
37
|
-
'__webdriver_script_func',
|
|
38
|
-
'__fxdriver_evaluate',
|
|
39
|
-
'__fxdriver_unwrap',
|
|
40
|
-
'webdriver',
|
|
41
|
-
];
|
|
42
|
-
|
|
43
|
-
for (const global of seleniumGlobals) {
|
|
44
|
-
if (global in window) {
|
|
45
|
-
indicators.push(`global-${global}`);
|
|
46
|
-
confidence = Math.max(confidence, 1.0);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Check for Selenium document properties
|
|
51
|
-
const seleniumDocProps = [
|
|
52
|
-
'__webdriver_script_fn',
|
|
53
|
-
'__driver_evaluate',
|
|
54
|
-
'__webdriver_evaluate',
|
|
55
|
-
'__selenium_evaluate',
|
|
56
|
-
'__fxdriver_evaluate',
|
|
57
|
-
'__driver_unwrap',
|
|
58
|
-
'__webdriver_unwrap',
|
|
59
|
-
'__selenium_unwrap',
|
|
60
|
-
'__fxdriver_unwrap',
|
|
61
|
-
];
|
|
62
|
-
|
|
63
|
-
for (const prop of seleniumDocProps) {
|
|
64
|
-
if (prop in document) {
|
|
65
|
-
indicators.push(`document-${prop}`);
|
|
66
|
-
confidence = Math.max(confidence, 1.0);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Check for ChromeDriver artifacts ($cdc variables)
|
|
71
|
-
const windowKeys = Object.keys(window);
|
|
72
|
-
|
|
73
|
-
// ChromeDriver injects variables starting with $cdc_ or $wdc_
|
|
74
|
-
const cdcVars = windowKeys.filter(key =>
|
|
75
|
-
key.startsWith('$cdc_') ||
|
|
76
|
-
key.startsWith('$wdc_') ||
|
|
77
|
-
key.startsWith('$chrome_asyncScriptInfo')
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
if (cdcVars.length > 0) {
|
|
81
|
-
indicators.push('chromedriver-variables');
|
|
82
|
-
confidence = Math.max(confidence, 1.0);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Check for GeckoDriver (Firefox) artifacts
|
|
86
|
-
if (window.webdriverCallback || document.documentElement.getAttribute('webdriver')) {
|
|
87
|
-
indicators.push('geckodriver-artifacts');
|
|
88
|
-
confidence = Math.max(confidence, 1.0);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Check for webdriver in document element attributes
|
|
92
|
-
try {
|
|
93
|
-
const docElement = document.documentElement;
|
|
94
|
-
if (docElement.hasAttribute('webdriver') ||
|
|
95
|
-
docElement.getAttribute('selenium') ||
|
|
96
|
-
docElement.getAttribute('driver')) {
|
|
97
|
-
indicators.push('document-webdriver-attr');
|
|
98
|
-
confidence = Math.max(confidence, 1.0);
|
|
99
|
-
}
|
|
100
|
-
} catch (e) {
|
|
101
|
-
// Ignore errors
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Check for Selenium IDE artifacts
|
|
105
|
-
if (window.selenium || window.sideex) {
|
|
106
|
-
indicators.push('selenium-ide');
|
|
107
|
-
confidence = Math.max(confidence, 1.0);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Check for navigator.webdriver property descriptor anomalies
|
|
111
|
-
try {
|
|
112
|
-
const descriptor = Object.getOwnPropertyDescriptor(Navigator.prototype, 'webdriver');
|
|
113
|
-
if (descriptor) {
|
|
114
|
-
// Check if the getter has been modified
|
|
115
|
-
if (descriptor.get) {
|
|
116
|
-
const getterStr = descriptor.get.toString();
|
|
117
|
-
if (!getterStr.includes('[native code]')) {
|
|
118
|
-
indicators.push('webdriver-getter-modified');
|
|
119
|
-
confidence = Math.max(confidence, 0.7);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
} catch (e) {
|
|
124
|
-
// Ignore errors
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Check for driver command executor
|
|
128
|
-
if (window.domAutomation || window.domAutomationController) {
|
|
129
|
-
indicators.push('dom-automation');
|
|
130
|
-
confidence = Math.max(confidence, 1.0);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Check for callPhantom alternative used by some Selenium setups
|
|
134
|
-
if (window.awesomium) {
|
|
135
|
-
indicators.push('awesomium');
|
|
136
|
-
confidence = Math.max(confidence, 0.9);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Check for external interface (used by some automation tools)
|
|
140
|
-
if (window.external && window.external.toString().includes('Selenium')) {
|
|
141
|
-
indicators.push('external-selenium');
|
|
142
|
-
confidence = Math.max(confidence, 1.0);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const triggered = indicators.length > 0;
|
|
146
|
-
|
|
147
|
-
return this.createResult(triggered, { indicators }, confidence);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
export { SeleniumSignal };
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Automation signals index.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export { PuppeteerSignal } from './PuppeteerSignal.js';
|
|
6
|
-
export { PlaywrightSignal } from './PlaywrightSignal.js';
|
|
7
|
-
export { SeleniumSignal } from './SeleniumSignal.js';
|
|
8
|
-
export { PhantomJSSignal } from './PhantomJSSignal.js';
|