@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.
Files changed (38) hide show
  1. package/README.md +22 -0
  2. package/dist/bot-detector.cjs.js +115 -75
  3. package/dist/bot-detector.cjs.js.map +2 -2
  4. package/dist/bot-detector.esm.js +115 -75
  5. package/dist/bot-detector.esm.js.map +2 -2
  6. package/dist/bot-detector.iife.js +115 -75
  7. package/dist/bot-detector.iife.js.map +2 -2
  8. package/dist/bot-detector.iife.min.js +3 -1
  9. package/package.json +1 -2
  10. package/src/core/BotDetector.js +0 -284
  11. package/src/core/ScoringEngine.js +0 -134
  12. package/src/core/Signal.js +0 -181
  13. package/src/core/VerdictEngine.js +0 -132
  14. package/src/index.js +0 -273
  15. package/src/signals/automation/PhantomJSSignal.js +0 -137
  16. package/src/signals/automation/PlaywrightSignal.js +0 -129
  17. package/src/signals/automation/PuppeteerSignal.js +0 -122
  18. package/src/signals/automation/SeleniumSignal.js +0 -151
  19. package/src/signals/automation/index.js +0 -8
  20. package/src/signals/behavior/InteractionTimingSignal.js +0 -170
  21. package/src/signals/behavior/KeyboardPatternSignal.js +0 -235
  22. package/src/signals/behavior/MouseMovementSignal.js +0 -215
  23. package/src/signals/behavior/ScrollBehaviorSignal.js +0 -236
  24. package/src/signals/behavior/index.js +0 -8
  25. package/src/signals/environment/HeadlessSignal.js +0 -97
  26. package/src/signals/environment/NavigatorAnomalySignal.js +0 -117
  27. package/src/signals/environment/PermissionsSignal.js +0 -76
  28. package/src/signals/environment/WebDriverSignal.js +0 -58
  29. package/src/signals/environment/index.js +0 -8
  30. package/src/signals/fingerprint/AudioContextSignal.js +0 -158
  31. package/src/signals/fingerprint/CanvasSignal.js +0 -133
  32. package/src/signals/fingerprint/PluginsSignal.js +0 -106
  33. package/src/signals/fingerprint/ScreenSignal.js +0 -157
  34. package/src/signals/fingerprint/WebGLSignal.js +0 -146
  35. package/src/signals/fingerprint/index.js +0 -9
  36. package/src/signals/timing/DOMContentTimingSignal.js +0 -159
  37. package/src/signals/timing/PageLoadSignal.js +0 -165
  38. 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';