is-antibot 1.2.0 → 1.3.1
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 +11 -0
- package/package.json +1 -1
- package/src/index.js +183 -15
package/README.md
CHANGED
|
@@ -22,6 +22,13 @@
|
|
|
22
22
|
- **Kasada** - Advanced bot mitigation
|
|
23
23
|
- **Imperva/Incapsula** - Web application firewall
|
|
24
24
|
- **AWS WAF** - Amazon Web Services Web Application Firewall
|
|
25
|
+
- **Reblaze** - Cloud-based web security platform
|
|
26
|
+
- **Cheq** - Bot detection and prevention
|
|
27
|
+
- **Sucuri** - Website security platform and WAF
|
|
28
|
+
- **ThreatMetrix** - LexisNexis fraud prevention and device fingerprinting
|
|
29
|
+
- **Meetrics** - User authenticity verification
|
|
30
|
+
- **Ocule** - Bot detection with advanced obfuscation
|
|
31
|
+
- **LinkedIn** - Bot filter protection
|
|
25
32
|
|
|
26
33
|
### CAPTCHA Providers
|
|
27
34
|
|
|
@@ -30,6 +37,10 @@
|
|
|
30
37
|
- **FunCaptcha** - Arkose Labs interactive challenges
|
|
31
38
|
- **GeeTest** - AI-powered CAPTCHA
|
|
32
39
|
- **Cloudflare Turnstile** - Privacy-preserving CAPTCHA alternative
|
|
40
|
+
- **Friendly Captcha** - GDPR-compliant privacy-first CAPTCHA
|
|
41
|
+
- **Captcha.eu** - European GDPR-compliant CAPTCHA service
|
|
42
|
+
- **QCloud Captcha** - Tencent Cloud CAPTCHA service
|
|
43
|
+
- **AliExpress CAPTCHA** - AliExpress x5sec security challenge
|
|
33
44
|
|
|
34
45
|
## Why
|
|
35
46
|
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "is-antibot",
|
|
3
3
|
"description": "Identify if a response is an antibot challenge from CloudFlare, Akamai, DataDome, Vercel, PerimeterX, Shape Security, and more, including CAPTCHA providers like reCAPTCHA and hCaptcha.",
|
|
4
4
|
"homepage": "https://github.com/microlinkhq/is-antibot",
|
|
5
|
-
"version": "1.
|
|
5
|
+
"version": "1.3.1",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": "./src/index.js"
|
|
8
8
|
},
|
package/src/index.js
CHANGED
|
@@ -35,6 +35,11 @@ module.exports = ({ headers = {}, body = '', url = '' } = {}) => {
|
|
|
35
35
|
return createResult(true, 'cloudflare')
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
// Cloudflare: cf_clearance cookie indicates Cloudflare challenge flow
|
|
39
|
+
if (testSetCookie(headers, 'cf_clearance=')) {
|
|
40
|
+
return createResult(true, 'cloudflare')
|
|
41
|
+
}
|
|
42
|
+
|
|
38
43
|
// Vercel: Check for x-vercel-mitigated header with 'challenge' value
|
|
39
44
|
// Solver reference: https://github.com/glizzykingdreko/Vercel-Attack-Mode-Solver
|
|
40
45
|
if (getHeader(headers, 'x-vercel-mitigated') === 'challenge') {
|
|
@@ -56,17 +61,30 @@ module.exports = ({ headers = {}, body = '', url = '' } = {}) => {
|
|
|
56
61
|
return createResult(true, 'akamai')
|
|
57
62
|
}
|
|
58
63
|
|
|
64
|
+
// Akamai: _abck bot manager tracking cookie
|
|
65
|
+
if (testSetCookie(headers, '_abck=')) {
|
|
66
|
+
return createResult(true, 'akamai')
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Akamai: Bot Manager API namespace (bmak) in body
|
|
70
|
+
if (body && testPattern(body, 'bmak.')) {
|
|
71
|
+
return createResult(true, 'akamai')
|
|
72
|
+
}
|
|
73
|
+
|
|
59
74
|
// DataDome: Check for x-dd-b header with values '1' (soft challenge) or '2' (hard challenge/CAPTCHA)
|
|
60
75
|
// Official docs: https://docs.datadome.co/reference/validate-request
|
|
61
|
-
// 1: Soft challenge / JS redirect / interstitial
|
|
62
|
-
// 2: Hard challenge / HTML redirect / CAPTCHA
|
|
63
76
|
if (['1', '2'].includes(getHeader(headers, 'x-dd-b'))) {
|
|
64
77
|
return createResult(true, 'datadome')
|
|
65
78
|
}
|
|
66
79
|
|
|
67
|
-
// DataDome: Check for x-datadome header presence
|
|
80
|
+
// DataDome: Check for x-datadome or x-datadome-cid header presence
|
|
68
81
|
// Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/antibot/datadome.json
|
|
69
|
-
if (getHeader(headers, 'x-datadome')) {
|
|
82
|
+
if (getHeader(headers, 'x-datadome') || getHeader(headers, 'x-datadome-cid')) {
|
|
83
|
+
return createResult(true, 'datadome')
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// DataDome: datadome tracking cookie
|
|
87
|
+
if (testSetCookie(headers, 'datadome=')) {
|
|
70
88
|
return createResult(true, 'datadome')
|
|
71
89
|
}
|
|
72
90
|
|
|
@@ -76,9 +94,19 @@ module.exports = ({ headers = {}, body = '', url = '' } = {}) => {
|
|
|
76
94
|
return createResult(true, 'perimeterx')
|
|
77
95
|
}
|
|
78
96
|
|
|
79
|
-
// PerimeterX: Check for window._pxAppId in body
|
|
97
|
+
// PerimeterX: Check for window._pxAppId, pxInit, or _pxAction in body
|
|
80
98
|
// Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/antibot/perimeterx.json#L130-L137
|
|
81
|
-
if (
|
|
99
|
+
if (
|
|
100
|
+
body &&
|
|
101
|
+
(testPattern(body, 'window._pxAppId') ||
|
|
102
|
+
testPattern(body, 'pxInit') ||
|
|
103
|
+
testPattern(body, '_pxAction'))
|
|
104
|
+
) {
|
|
105
|
+
return createResult(true, 'perimeterx')
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// PerimeterX: _px3 or _pxhd cookies
|
|
109
|
+
if (testSetCookie(headers, '_px3=') || testSetCookie(headers, '_pxhd=')) {
|
|
82
110
|
return createResult(true, 'perimeterx')
|
|
83
111
|
}
|
|
84
112
|
|
|
@@ -134,12 +162,95 @@ module.exports = ({ headers = {}, body = '', url = '' } = {}) => {
|
|
|
134
162
|
return createResult(true, 'imperva')
|
|
135
163
|
}
|
|
136
164
|
|
|
137
|
-
//
|
|
165
|
+
// Imperva/Incapsula: incap_ses_, visid_incap_, or reese84 cookies
|
|
166
|
+
// Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/antibot/incapsula.json
|
|
167
|
+
if (
|
|
168
|
+
testSetCookie(headers, 'incap_ses_') ||
|
|
169
|
+
testSetCookie(headers, 'visid_incap_') ||
|
|
170
|
+
testSetCookie(headers, 'reese84=')
|
|
171
|
+
) {
|
|
172
|
+
return createResult(true, 'imperva')
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Reblaze: rbzid or rbzsessionid cookies
|
|
176
|
+
// Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/antibot/reblaze.json
|
|
177
|
+
if (
|
|
178
|
+
testSetCookie(headers, 'rbzid=') ||
|
|
179
|
+
testSetCookie(headers, 'rbzsessionid=')
|
|
180
|
+
) {
|
|
181
|
+
return createResult(true, 'reblaze')
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Reblaze: Check for 'reblaze' text in response body
|
|
185
|
+
if (body && testPattern(body, 'reblaze')) {
|
|
186
|
+
return createResult(true, 'reblaze')
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Cheq: Check for CheqSdk or cheqzone.com in body
|
|
190
|
+
// Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/antibot/cheq.json
|
|
191
|
+
if (
|
|
192
|
+
body &&
|
|
193
|
+
(testPattern(body, 'CheqSdk') || testPattern(body, 'cheqzone.com'))
|
|
194
|
+
) {
|
|
195
|
+
return createResult(true, 'cheq')
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Cheq: Check for cheqzone.com or cheq.ai in URL
|
|
199
|
+
if (
|
|
200
|
+
url &&
|
|
201
|
+
(testPattern(url, 'cheqzone\\.com', true) ||
|
|
202
|
+
testPattern(url, 'cheq\\.ai', true))
|
|
203
|
+
) {
|
|
204
|
+
return createResult(true, 'cheq')
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Sucuri: Check for 'sucuri' text in response body
|
|
208
|
+
// Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/antibot/sucuri.json
|
|
209
|
+
if (body && testPattern(body, 'sucuri')) {
|
|
210
|
+
return createResult(true, 'sucuri')
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ThreatMetrix: Check for 'ThreatMetrix' in body
|
|
214
|
+
// Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/antibot/threatmetrix.json
|
|
215
|
+
if (body && testPattern(body, 'ThreatMetrix')) {
|
|
216
|
+
return createResult(true, 'threatmetrix')
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ThreatMetrix: Check for fp/check.js fingerprint endpoint in URL
|
|
220
|
+
if (url && testPattern(url, 'fp/check.js')) {
|
|
221
|
+
return createResult(true, 'threatmetrix')
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Meetrics: Check for 'meetrics' text in response body
|
|
225
|
+
// Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/antibot/meetrics.json
|
|
226
|
+
if (body && testPattern(body, 'meetrics')) {
|
|
227
|
+
return createResult(true, 'meetrics')
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Meetrics: Check for meetrics.com in URL
|
|
231
|
+
if (url && testPattern(url, 'meetrics\\.com', true)) {
|
|
232
|
+
return createResult(true, 'meetrics')
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Ocule: Check for ocule.co.uk in body
|
|
236
|
+
// Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/antibot/ocule.json
|
|
237
|
+
if (body && testPattern(body, 'ocule.co.uk')) {
|
|
238
|
+
return createResult(true, 'ocule')
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Ocule: Check for ocule.co.uk in URL
|
|
242
|
+
if (url && testPattern(url, 'ocule\\.co\\.uk', true)) {
|
|
243
|
+
return createResult(true, 'ocule')
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// reCAPTCHA: Check for recaptcha/api, google.com/recaptcha, gstatic.com/recaptcha, or recaptcha.net in URL
|
|
138
247
|
// Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/captcha/recaptcha.json#L13-L48
|
|
139
248
|
if (
|
|
140
249
|
url &&
|
|
141
250
|
(testPattern(url, 'recaptcha/api') ||
|
|
142
|
-
testPattern(url, 'google\\.com/recaptcha', true)
|
|
251
|
+
testPattern(url, 'google\\.com/recaptcha', true) ||
|
|
252
|
+
testPattern(url, 'gstatic.com/recaptcha') ||
|
|
253
|
+
testPattern(url, 'recaptcha.net'))
|
|
143
254
|
) {
|
|
144
255
|
return createResult(true, 'recaptcha')
|
|
145
256
|
}
|
|
@@ -212,7 +323,6 @@ module.exports = ({ headers = {}, body = '', url = '' } = {}) => {
|
|
|
212
323
|
}
|
|
213
324
|
|
|
214
325
|
// Cloudflare Turnstile: Check for challenges.cloudflare.com/turnstile in URL
|
|
215
|
-
// Turnstile is Cloudflare's CAPTCHA alternative with privacy focus
|
|
216
326
|
if (
|
|
217
327
|
url &&
|
|
218
328
|
testPattern(url, 'challenges\\.cloudflare\\.com/turnstile', true)
|
|
@@ -220,23 +330,77 @@ module.exports = ({ headers = {}, body = '', url = '' } = {}) => {
|
|
|
220
330
|
return createResult(true, 'cloudflare-turnstile')
|
|
221
331
|
}
|
|
222
332
|
|
|
223
|
-
// Cloudflare Turnstile: Check for cf-turnstile class in body
|
|
333
|
+
// Cloudflare Turnstile: Check for cf-turnstile class in body
|
|
224
334
|
if (body && testPattern(body, 'cf-turnstile')) {
|
|
225
335
|
return createResult(true, 'cloudflare-turnstile')
|
|
226
336
|
}
|
|
227
337
|
|
|
228
|
-
// Cloudflare Turnstile: Check for turnstile text in body
|
|
338
|
+
// Cloudflare Turnstile: Check for turnstile text in body
|
|
229
339
|
if (body && testPattern(body, 'turnstile')) {
|
|
230
340
|
return createResult(true, 'cloudflare-turnstile')
|
|
231
341
|
}
|
|
232
342
|
|
|
343
|
+
// Friendly Captcha: Check for friendlycaptcha.com in URL
|
|
344
|
+
// Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/captcha/friendlycaptcha.json
|
|
345
|
+
if (url && testPattern(url, 'friendlycaptcha\\.com', true)) {
|
|
346
|
+
return createResult(true, 'friendly-captcha')
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Friendly Captcha: Check for frc-captcha container or friendlyChallenge object in body
|
|
350
|
+
if (
|
|
351
|
+
body &&
|
|
352
|
+
(testPattern(body, 'frc-captcha') ||
|
|
353
|
+
testPattern(body, 'friendlyChallenge'))
|
|
354
|
+
) {
|
|
355
|
+
return createResult(true, 'friendly-captcha')
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Captcha.eu: Check for captcha.eu in URL
|
|
359
|
+
// Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/captcha/captchaeu.json
|
|
360
|
+
if (url && testPattern(url, 'captcha\\.eu', true)) {
|
|
361
|
+
return createResult(true, 'captcha-eu')
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Captcha.eu: Check for CaptchaEU or captchaeu in body
|
|
365
|
+
if (
|
|
366
|
+
body &&
|
|
367
|
+
(testPattern(body, 'CaptchaEU') || testPattern(body, 'captchaeu'))
|
|
368
|
+
) {
|
|
369
|
+
return createResult(true, 'captcha-eu')
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// QCloud Captcha (Tencent): Check for turing.captcha.qcloud.com in URL
|
|
373
|
+
// Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/captcha/qcloud.json
|
|
374
|
+
if (url && testPattern(url, 'turing\\.captcha\\.qcloud\\.com', true)) {
|
|
375
|
+
return createResult(true, 'qcloud-captcha')
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// QCloud Captcha: Check for TencentCaptcha or turing.captcha in body
|
|
379
|
+
if (
|
|
380
|
+
body &&
|
|
381
|
+
(testPattern(body, 'TencentCaptcha') ||
|
|
382
|
+
testPattern(body, 'turing.captcha'))
|
|
383
|
+
) {
|
|
384
|
+
return createResult(true, 'qcloud-captcha')
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// AliExpress CAPTCHA: Check for punish?x5secdata in URL
|
|
388
|
+
// Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/captcha/aliexpress.json
|
|
389
|
+
if (url && testPattern(url, 'punish\\?x5secdata', true)) {
|
|
390
|
+
return createResult(true, 'aliexpress-captcha')
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// AliExpress CAPTCHA: Check for x5secdata in body
|
|
394
|
+
if (body && testPattern(body, 'x5secdata')) {
|
|
395
|
+
return createResult(true, 'aliexpress-captcha')
|
|
396
|
+
}
|
|
397
|
+
|
|
233
398
|
// LinkedIn: trkCode=bf cookie ("bot filter") is set when LinkedIn blocks a request
|
|
234
399
|
if (testSetCookie(headers, 'trkCode=bf')) {
|
|
235
400
|
return createResult(true, 'linkedin')
|
|
236
401
|
}
|
|
237
402
|
|
|
238
403
|
// AWS WAF: Check for x-amzn-waf-action or x-amzn-requestid headers
|
|
239
|
-
// These headers are set by AWS WAF when bot control rules are triggered
|
|
240
404
|
// Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/antibot/aws-waf.json
|
|
241
405
|
if (
|
|
242
406
|
getHeader(headers, 'x-amzn-waf-action') ||
|
|
@@ -245,9 +409,13 @@ module.exports = ({ headers = {}, body = '', url = '' } = {}) => {
|
|
|
245
409
|
return createResult(true, 'aws-waf')
|
|
246
410
|
}
|
|
247
411
|
|
|
248
|
-
// AWS WAF: Check for aws-waf text in body
|
|
249
|
-
|
|
250
|
-
|
|
412
|
+
// AWS WAF: Check for aws-waf or awswaf text in body
|
|
413
|
+
if (body && (testPattern(body, 'aws-waf') || testPattern(body, 'awswaf'))) {
|
|
414
|
+
return createResult(true, 'aws-waf')
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// AWS WAF: aws-waf-token cookie
|
|
418
|
+
if (testSetCookie(headers, 'aws-waf-token=')) {
|
|
251
419
|
return createResult(true, 'aws-waf')
|
|
252
420
|
}
|
|
253
421
|
|