nextjs-secure 0.6.0 → 0.7.0

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/dist/bot.cjs ADDED
@@ -0,0 +1,1521 @@
1
+ 'use strict';
2
+
3
+ // src/middleware/bot/user-agent.ts
4
+ var KNOWN_BOT_PATTERNS = [
5
+ // Search Engines - Generally allowed
6
+ {
7
+ name: "Googlebot",
8
+ pattern: /Googlebot|Google-InspectionTool|Storebot-Google|GoogleOther/i,
9
+ category: "search_engine",
10
+ allowed: true,
11
+ description: "Google search crawler"
12
+ },
13
+ {
14
+ name: "Bingbot",
15
+ pattern: /bingbot|msnbot|BingPreview/i,
16
+ category: "search_engine",
17
+ allowed: true,
18
+ description: "Microsoft Bing crawler"
19
+ },
20
+ {
21
+ name: "Yahoo Slurp",
22
+ pattern: /Slurp/i,
23
+ category: "search_engine",
24
+ allowed: true,
25
+ description: "Yahoo search crawler"
26
+ },
27
+ {
28
+ name: "DuckDuckBot",
29
+ pattern: /DuckDuckBot|DuckDuckGo-Favicons-Bot/i,
30
+ category: "search_engine",
31
+ allowed: true,
32
+ description: "DuckDuckGo crawler"
33
+ },
34
+ {
35
+ name: "Baiduspider",
36
+ pattern: /Baiduspider/i,
37
+ category: "search_engine",
38
+ allowed: true,
39
+ description: "Baidu search crawler"
40
+ },
41
+ {
42
+ name: "Yandex",
43
+ pattern: /YandexBot|YandexImages|YandexMobileBot/i,
44
+ category: "search_engine",
45
+ allowed: true,
46
+ description: "Yandex search crawler"
47
+ },
48
+ {
49
+ name: "Applebot",
50
+ pattern: /Applebot/i,
51
+ category: "search_engine",
52
+ allowed: true,
53
+ description: "Apple search crawler"
54
+ },
55
+ {
56
+ name: "Sogou",
57
+ pattern: /Sogou/i,
58
+ category: "search_engine",
59
+ allowed: true,
60
+ description: "Sogou search crawler"
61
+ },
62
+ {
63
+ name: "Exabot",
64
+ pattern: /Exabot/i,
65
+ category: "search_engine",
66
+ allowed: true,
67
+ description: "Exalead search crawler"
68
+ },
69
+ // Social Media - Generally allowed
70
+ {
71
+ name: "Facebook",
72
+ pattern: /facebookexternalhit|Facebot|facebookcatalog/i,
73
+ category: "social_media",
74
+ allowed: true,
75
+ description: "Facebook crawler for link previews"
76
+ },
77
+ {
78
+ name: "Twitter",
79
+ pattern: /Twitterbot/i,
80
+ category: "social_media",
81
+ allowed: true,
82
+ description: "Twitter/X crawler for cards"
83
+ },
84
+ {
85
+ name: "LinkedIn",
86
+ pattern: /LinkedInBot/i,
87
+ category: "social_media",
88
+ allowed: true,
89
+ description: "LinkedIn crawler for previews"
90
+ },
91
+ {
92
+ name: "Pinterest",
93
+ pattern: /Pinterest|Pinterestbot/i,
94
+ category: "social_media",
95
+ allowed: true,
96
+ description: "Pinterest crawler"
97
+ },
98
+ {
99
+ name: "Slack",
100
+ pattern: /Slackbot/i,
101
+ category: "social_media",
102
+ allowed: true,
103
+ description: "Slack link preview bot"
104
+ },
105
+ {
106
+ name: "Discord",
107
+ pattern: /Discordbot/i,
108
+ category: "social_media",
109
+ allowed: true,
110
+ description: "Discord link preview bot"
111
+ },
112
+ {
113
+ name: "Telegram",
114
+ pattern: /TelegramBot/i,
115
+ category: "social_media",
116
+ allowed: true,
117
+ description: "Telegram link preview bot"
118
+ },
119
+ {
120
+ name: "WhatsApp",
121
+ pattern: /WhatsApp/i,
122
+ category: "social_media",
123
+ allowed: true,
124
+ description: "WhatsApp link preview"
125
+ },
126
+ {
127
+ name: "Snapchat",
128
+ pattern: /Snapchat/i,
129
+ category: "social_media",
130
+ allowed: true,
131
+ description: "Snapchat crawler"
132
+ },
133
+ // Monitoring - Generally allowed
134
+ {
135
+ name: "UptimeRobot",
136
+ pattern: /UptimeRobot/i,
137
+ category: "monitoring",
138
+ allowed: true,
139
+ description: "UptimeRobot monitoring"
140
+ },
141
+ {
142
+ name: "Pingdom",
143
+ pattern: /Pingdom/i,
144
+ category: "monitoring",
145
+ allowed: true,
146
+ description: "Pingdom monitoring"
147
+ },
148
+ {
149
+ name: "StatusCake",
150
+ pattern: /StatusCake/i,
151
+ category: "monitoring",
152
+ allowed: true,
153
+ description: "StatusCake monitoring"
154
+ },
155
+ {
156
+ name: "Site24x7",
157
+ pattern: /Site24x7/i,
158
+ category: "monitoring",
159
+ allowed: true,
160
+ description: "Site24x7 monitoring"
161
+ },
162
+ {
163
+ name: "Datadog",
164
+ pattern: /Datadog/i,
165
+ category: "monitoring",
166
+ allowed: true,
167
+ description: "Datadog synthetic monitoring"
168
+ },
169
+ {
170
+ name: "New Relic",
171
+ pattern: /NewRelicPinger/i,
172
+ category: "monitoring",
173
+ allowed: true,
174
+ description: "New Relic synthetic monitoring"
175
+ },
176
+ {
177
+ name: "Checkly",
178
+ pattern: /Checkly/i,
179
+ category: "monitoring",
180
+ allowed: true,
181
+ description: "Checkly monitoring"
182
+ },
183
+ // SEO Tools - Usually allowed but can be blocked
184
+ {
185
+ name: "Ahrefs",
186
+ pattern: /AhrefsBot|AhrefsSiteAudit/i,
187
+ category: "seo",
188
+ allowed: false,
189
+ description: "Ahrefs SEO crawler"
190
+ },
191
+ {
192
+ name: "Semrush",
193
+ pattern: /SemrushBot/i,
194
+ category: "seo",
195
+ allowed: false,
196
+ description: "Semrush SEO crawler"
197
+ },
198
+ {
199
+ name: "Moz",
200
+ pattern: /rogerbot|DotBot/i,
201
+ category: "seo",
202
+ allowed: false,
203
+ description: "Moz SEO crawler"
204
+ },
205
+ {
206
+ name: "Majestic",
207
+ pattern: /MJ12bot/i,
208
+ category: "seo",
209
+ allowed: false,
210
+ description: "Majestic SEO crawler"
211
+ },
212
+ {
213
+ name: "Screaming Frog",
214
+ pattern: /Screaming Frog/i,
215
+ category: "seo",
216
+ allowed: false,
217
+ description: "Screaming Frog SEO Spider"
218
+ },
219
+ // AI Crawlers - Configurable
220
+ {
221
+ name: "GPTBot",
222
+ pattern: /GPTBot/i,
223
+ category: "ai_crawler",
224
+ allowed: false,
225
+ description: "OpenAI GPT training crawler"
226
+ },
227
+ {
228
+ name: "ChatGPT-User",
229
+ pattern: /ChatGPT-User/i,
230
+ category: "ai_crawler",
231
+ allowed: false,
232
+ description: "ChatGPT user browsing"
233
+ },
234
+ {
235
+ name: "Claude-Web",
236
+ pattern: /Claude-Web|ClaudeBot|anthropic-ai/i,
237
+ category: "ai_crawler",
238
+ allowed: false,
239
+ description: "Anthropic Claude crawler"
240
+ },
241
+ {
242
+ name: "Bytespider",
243
+ pattern: /Bytespider/i,
244
+ category: "ai_crawler",
245
+ allowed: false,
246
+ description: "ByteDance AI crawler"
247
+ },
248
+ {
249
+ name: "CCBot",
250
+ pattern: /CCBot/i,
251
+ category: "ai_crawler",
252
+ allowed: false,
253
+ description: "Common Crawl bot"
254
+ },
255
+ {
256
+ name: "Google-Extended",
257
+ pattern: /Google-Extended/i,
258
+ category: "ai_crawler",
259
+ allowed: false,
260
+ description: "Google AI training crawler"
261
+ },
262
+ {
263
+ name: "Cohere-ai",
264
+ pattern: /cohere-ai/i,
265
+ category: "ai_crawler",
266
+ allowed: false,
267
+ description: "Cohere AI crawler"
268
+ },
269
+ {
270
+ name: "PerplexityBot",
271
+ pattern: /PerplexityBot/i,
272
+ category: "ai_crawler",
273
+ allowed: false,
274
+ description: "Perplexity AI crawler"
275
+ },
276
+ // Feed Readers - Generally allowed
277
+ {
278
+ name: "Feedly",
279
+ pattern: /Feedly/i,
280
+ category: "feed_reader",
281
+ allowed: true,
282
+ description: "Feedly RSS reader"
283
+ },
284
+ {
285
+ name: "Feedbin",
286
+ pattern: /Feedbin/i,
287
+ category: "feed_reader",
288
+ allowed: true,
289
+ description: "Feedbin RSS reader"
290
+ },
291
+ {
292
+ name: "NewsBlur",
293
+ pattern: /NewsBlur/i,
294
+ category: "feed_reader",
295
+ allowed: true,
296
+ description: "NewsBlur RSS reader"
297
+ },
298
+ // Link Previews - Generally allowed
299
+ {
300
+ name: "Embedly",
301
+ pattern: /Embedly/i,
302
+ category: "preview",
303
+ allowed: true,
304
+ description: "Embedly link preview"
305
+ },
306
+ {
307
+ name: "Iframely",
308
+ pattern: /Iframely/i,
309
+ category: "preview",
310
+ allowed: true,
311
+ description: "Iframely link preview"
312
+ },
313
+ // Security Scanners - Block by default
314
+ {
315
+ name: "Nessus",
316
+ pattern: /Nessus/i,
317
+ category: "security",
318
+ allowed: false,
319
+ description: "Nessus vulnerability scanner"
320
+ },
321
+ {
322
+ name: "Nikto",
323
+ pattern: /Nikto/i,
324
+ category: "security",
325
+ allowed: false,
326
+ description: "Nikto web scanner"
327
+ },
328
+ {
329
+ name: "sqlmap",
330
+ pattern: /sqlmap/i,
331
+ category: "security",
332
+ allowed: false,
333
+ description: "sqlmap SQL injection tool"
334
+ },
335
+ {
336
+ name: "WPScan",
337
+ pattern: /WPScan/i,
338
+ category: "security",
339
+ allowed: false,
340
+ description: "WordPress vulnerability scanner"
341
+ },
342
+ {
343
+ name: "Acunetix",
344
+ pattern: /Acunetix/i,
345
+ category: "security",
346
+ allowed: false,
347
+ description: "Acunetix vulnerability scanner"
348
+ },
349
+ // Known Scrapers - Block
350
+ {
351
+ name: "Scrapy",
352
+ pattern: /Scrapy/i,
353
+ category: "scraper",
354
+ allowed: false,
355
+ description: "Scrapy web scraper"
356
+ },
357
+ {
358
+ name: "HTTrack",
359
+ pattern: /HTTrack/i,
360
+ category: "scraper",
361
+ allowed: false,
362
+ description: "HTTrack website copier"
363
+ },
364
+ {
365
+ name: "WebCopier",
366
+ pattern: /WebCopier/i,
367
+ category: "scraper",
368
+ allowed: false,
369
+ description: "WebCopier tool"
370
+ },
371
+ {
372
+ name: "SiteSnagger",
373
+ pattern: /SiteSnagger/i,
374
+ category: "scraper",
375
+ allowed: false,
376
+ description: "SiteSnagger downloader"
377
+ },
378
+ // Spam Bots - Always block
379
+ {
380
+ name: "Spam Bot",
381
+ pattern: /spam|harvester|extractor|collect/i,
382
+ category: "spam",
383
+ allowed: false,
384
+ description: "Generic spam bot pattern"
385
+ },
386
+ {
387
+ name: "Email Harvester",
388
+ pattern: /email.*harvest|harvest.*email/i,
389
+ category: "spam",
390
+ allowed: false,
391
+ description: "Email harvesting bot"
392
+ },
393
+ // Malicious - Always block
394
+ {
395
+ name: "Malicious Generic",
396
+ pattern: /masscan|ZmEu|morfeus|nmap/i,
397
+ category: "malicious",
398
+ allowed: false,
399
+ description: "Known malicious tools"
400
+ },
401
+ {
402
+ name: "Vulnerability Scanner",
403
+ pattern: /havij|w3af|webscarab/i,
404
+ category: "malicious",
405
+ allowed: false,
406
+ description: "Vulnerability scanning tools"
407
+ },
408
+ // Generic Bot Pattern
409
+ {
410
+ name: "Generic Bot",
411
+ pattern: /bot|crawl|spider|scrape|fetch/i,
412
+ category: "unknown",
413
+ allowed: false,
414
+ description: "Generic bot pattern"
415
+ },
416
+ {
417
+ name: "HTTP Library",
418
+ pattern: /python-requests|python-urllib|curl|wget|axios|node-fetch|got\//i,
419
+ category: "unknown",
420
+ allowed: false,
421
+ description: "HTTP library user agent"
422
+ }
423
+ ];
424
+ var DEFAULT_ALLOWED_CATEGORIES = [
425
+ "search_engine",
426
+ "social_media",
427
+ "monitoring",
428
+ "feed_reader",
429
+ "preview"
430
+ ];
431
+ var DEFAULT_ALLOWED_BOTS = [
432
+ "Googlebot",
433
+ "Bingbot",
434
+ "Yahoo Slurp",
435
+ "DuckDuckBot",
436
+ "Applebot",
437
+ "Facebook",
438
+ "Twitter",
439
+ "LinkedIn",
440
+ "Slack",
441
+ "Discord",
442
+ "UptimeRobot",
443
+ "Pingdom"
444
+ ];
445
+ function analyzeUserAgent(userAgent, options = {}) {
446
+ const {
447
+ blockAllBots = false,
448
+ allowCategories = DEFAULT_ALLOWED_CATEGORIES,
449
+ allowList = DEFAULT_ALLOWED_BOTS,
450
+ blockList = [],
451
+ customPatterns = [],
452
+ blockEmptyUA = true,
453
+ blockSuspiciousUA = true
454
+ } = options;
455
+ if (!userAgent || userAgent.trim() === "") {
456
+ return {
457
+ isBot: blockEmptyUA,
458
+ category: "unknown",
459
+ confidence: blockEmptyUA ? 0.9 : 0,
460
+ reason: "Empty User-Agent",
461
+ userAgent: userAgent || ""
462
+ };
463
+ }
464
+ const allPatterns = [...customPatterns, ...KNOWN_BOT_PATTERNS];
465
+ let matchedPattern = null;
466
+ for (const pattern of allPatterns) {
467
+ if (pattern.pattern.test(userAgent)) {
468
+ matchedPattern = pattern;
469
+ break;
470
+ }
471
+ }
472
+ if (!matchedPattern && blockSuspiciousUA && isSuspiciousUA(userAgent)) {
473
+ return {
474
+ isBot: true,
475
+ category: "unknown",
476
+ confidence: 0.8,
477
+ reason: "Suspicious User-Agent pattern",
478
+ userAgent
479
+ };
480
+ }
481
+ if (!matchedPattern) {
482
+ return {
483
+ isBot: false,
484
+ confidence: 0.1,
485
+ reason: "No bot pattern matched",
486
+ userAgent
487
+ };
488
+ }
489
+ if (blockList.includes(matchedPattern.name)) {
490
+ return {
491
+ isBot: true,
492
+ category: matchedPattern.category,
493
+ name: matchedPattern.name,
494
+ confidence: 0.95,
495
+ reason: `Blocked bot: ${matchedPattern.name}`,
496
+ userAgent
497
+ };
498
+ }
499
+ if (blockAllBots) {
500
+ return {
501
+ isBot: true,
502
+ category: matchedPattern.category,
503
+ name: matchedPattern.name,
504
+ confidence: 0.9,
505
+ reason: `Bot blocked (blockAllBots mode): ${matchedPattern.name}`,
506
+ userAgent
507
+ };
508
+ }
509
+ if (allowList.includes(matchedPattern.name)) {
510
+ return {
511
+ isBot: true,
512
+ category: matchedPattern.category,
513
+ name: matchedPattern.name,
514
+ confidence: 0.95,
515
+ reason: `Allowed bot: ${matchedPattern.name}`,
516
+ userAgent
517
+ };
518
+ }
519
+ if (allowCategories.includes(matchedPattern.category)) {
520
+ return {
521
+ isBot: true,
522
+ category: matchedPattern.category,
523
+ name: matchedPattern.name,
524
+ confidence: 0.9,
525
+ reason: `Allowed category: ${matchedPattern.category}`,
526
+ userAgent
527
+ };
528
+ }
529
+ return {
530
+ isBot: true,
531
+ category: matchedPattern.category,
532
+ name: matchedPattern.name,
533
+ confidence: 0.85,
534
+ reason: matchedPattern.allowed ? `Allowed bot: ${matchedPattern.name}` : `Blocked bot: ${matchedPattern.name}`,
535
+ userAgent
536
+ };
537
+ }
538
+ function isSuspiciousUA(userAgent) {
539
+ if (userAgent.length < 10) {
540
+ return true;
541
+ }
542
+ if (/^[0-9a-f]{8,}$/i.test(userAgent)) {
543
+ return true;
544
+ }
545
+ const hasBrowserIndicator = /Mozilla|Chrome|Safari|Firefox|Edge|Opera|MSIE|Trident/i.test(userAgent);
546
+ const hasOSIndicator = /Windows|Mac|Linux|Android|iOS|iPhone|iPad/i.test(userAgent);
547
+ if (hasBrowserIndicator && !hasOSIndicator && userAgent.length < 50) {
548
+ return true;
549
+ }
550
+ if (/Chrome\/[0-4]\./i.test(userAgent) || /Firefox\/[0-3]\./i.test(userAgent)) {
551
+ return true;
552
+ }
553
+ return false;
554
+ }
555
+ function isBotAllowed(botName, options = {}) {
556
+ const {
557
+ blockAllBots = false,
558
+ allowCategories = DEFAULT_ALLOWED_CATEGORIES,
559
+ allowList = DEFAULT_ALLOWED_BOTS,
560
+ blockList = []
561
+ } = options;
562
+ if (blockList.includes(botName)) {
563
+ return false;
564
+ }
565
+ if (blockAllBots) {
566
+ return false;
567
+ }
568
+ if (allowList.includes(botName)) {
569
+ return true;
570
+ }
571
+ const pattern = KNOWN_BOT_PATTERNS.find((p) => p.name === botName);
572
+ if (pattern && allowCategories.includes(pattern.category)) {
573
+ return true;
574
+ }
575
+ return pattern?.allowed ?? false;
576
+ }
577
+ function getBotsByCategory(category) {
578
+ return KNOWN_BOT_PATTERNS.filter((p) => p.category === category);
579
+ }
580
+ function createBotPattern(name, pattern, category, allowed = false, description) {
581
+ return {
582
+ name,
583
+ pattern: typeof pattern === "string" ? new RegExp(pattern, "i") : pattern,
584
+ category,
585
+ allowed,
586
+ description
587
+ };
588
+ }
589
+
590
+ // src/middleware/bot/honeypot.ts
591
+ var DEFAULT_HONEYPOT_FIELDS = [
592
+ "_hp_email",
593
+ "_hp_name",
594
+ "_hp_website",
595
+ "_hp_phone",
596
+ "_hp_address",
597
+ "email_confirm",
598
+ "website_url",
599
+ "fax_number"
600
+ ];
601
+ var DEFAULT_HONEYPOT_OPTIONS = {
602
+ fieldName: "_hp_email",
603
+ additionalFields: [],
604
+ checkIn: ["body", "query"],
605
+ validate: void 0
606
+ };
607
+ async function checkHoneypot(req, options = {}) {
608
+ const {
609
+ fieldName = DEFAULT_HONEYPOT_OPTIONS.fieldName,
610
+ additionalFields = DEFAULT_HONEYPOT_OPTIONS.additionalFields,
611
+ checkIn = DEFAULT_HONEYPOT_OPTIONS.checkIn,
612
+ validate
613
+ } = options;
614
+ const allFields = [fieldName, ...additionalFields];
615
+ const filledFields = [];
616
+ if (checkIn.includes("query")) {
617
+ const url = new URL(req.url);
618
+ for (const field of allFields) {
619
+ const value = url.searchParams.get(field);
620
+ if (value !== null && value !== "") {
621
+ filledFields.push(`query:${field}`);
622
+ }
623
+ }
624
+ }
625
+ if (checkIn.includes("body") && hasBody(req)) {
626
+ try {
627
+ const body = await getRequestBody(req);
628
+ if (body && typeof body === "object") {
629
+ for (const field of allFields) {
630
+ const value = body[field];
631
+ if (value !== void 0 && value !== null && value !== "") {
632
+ if (validate && !validate(value)) {
633
+ continue;
634
+ }
635
+ filledFields.push(`body:${field}`);
636
+ }
637
+ }
638
+ }
639
+ } catch {
640
+ }
641
+ }
642
+ if (checkIn.includes("headers")) {
643
+ for (const field of allFields) {
644
+ const headerName = `x-${field.replace(/_/g, "-")}`;
645
+ const value = req.headers.get(headerName);
646
+ if (value !== null && value !== "") {
647
+ filledFields.push(`header:${headerName}`);
648
+ }
649
+ }
650
+ }
651
+ if (filledFields.length > 0) {
652
+ return {
653
+ isBot: true,
654
+ category: "spam",
655
+ confidence: 0.95,
656
+ reason: `Honeypot triggered: ${filledFields.join(", ")}`,
657
+ ip: getClientIP(req)
658
+ };
659
+ }
660
+ return {
661
+ isBot: false,
662
+ confidence: 0,
663
+ reason: "Honeypot check passed"
664
+ };
665
+ }
666
+ function withHoneypot(handler, options = {}) {
667
+ return async (req, ctx) => {
668
+ const result = await checkHoneypot(req, options);
669
+ if (result.isBot) {
670
+ return new Response(
671
+ JSON.stringify({
672
+ success: false,
673
+ error: "Request rejected"
674
+ }),
675
+ {
676
+ status: 403,
677
+ headers: { "Content-Type": "application/json" }
678
+ }
679
+ );
680
+ }
681
+ return handler(req, ctx);
682
+ };
683
+ }
684
+ function generateHoneypotHTML(options = {}) {
685
+ const {
686
+ fieldName = DEFAULT_HONEYPOT_OPTIONS.fieldName,
687
+ additionalFields = []
688
+ } = options;
689
+ const allFields = [fieldName, ...additionalFields];
690
+ const fields = allFields.map((field) => {
691
+ const style = getRandomHidingStyle();
692
+ const labelText = humanizeFieldName(field);
693
+ return `
694
+ <div style="${style}" aria-hidden="true" tabindex="-1">
695
+ <label for="${field}">${labelText}</label>
696
+ <input
697
+ type="text"
698
+ id="${field}"
699
+ name="${field}"
700
+ autocomplete="off"
701
+ tabindex="-1"
702
+ />
703
+ </div>`;
704
+ }).join("\n");
705
+ return `<!-- Honeypot fields - Do not fill these -->
706
+ ${fields}`;
707
+ }
708
+ function generateHoneypotCSS(options = {}) {
709
+ const {
710
+ fieldName = DEFAULT_HONEYPOT_OPTIONS.fieldName,
711
+ additionalFields = []
712
+ } = options;
713
+ const allFields = [fieldName, ...additionalFields];
714
+ const selectors = allFields.map((f) => `#${f}`).join(", ");
715
+ return `
716
+ /* Honeypot field hiding */
717
+ ${selectors} {
718
+ position: absolute !important;
719
+ left: -9999px !important;
720
+ top: -9999px !important;
721
+ opacity: 0 !important;
722
+ height: 0 !important;
723
+ width: 0 !important;
724
+ z-index: -1 !important;
725
+ pointer-events: none !important;
726
+ }
727
+ `.trim();
728
+ }
729
+ function hasBody(req) {
730
+ const method = req.method.toUpperCase();
731
+ return ["POST", "PUT", "PATCH", "DELETE"].includes(method);
732
+ }
733
+ async function getRequestBody(req) {
734
+ try {
735
+ const contentType = req.headers.get("content-type") || "";
736
+ if (contentType.includes("application/json")) {
737
+ const cloned = req.clone();
738
+ return await cloned.json();
739
+ }
740
+ if (contentType.includes("application/x-www-form-urlencoded")) {
741
+ const cloned = req.clone();
742
+ const text = await cloned.text();
743
+ return Object.fromEntries(new URLSearchParams(text));
744
+ }
745
+ if (contentType.includes("multipart/form-data")) {
746
+ const cloned = req.clone();
747
+ const formData = await cloned.formData();
748
+ const obj = {};
749
+ formData.forEach((value, key) => {
750
+ obj[key] = value;
751
+ });
752
+ return obj;
753
+ }
754
+ return null;
755
+ } catch {
756
+ return null;
757
+ }
758
+ }
759
+ function getClientIP(req) {
760
+ return req.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || req.headers.get("x-real-ip") || req.headers.get("cf-connecting-ip") || "unknown";
761
+ }
762
+ function getRandomHidingStyle() {
763
+ const styles = [
764
+ "position: absolute; left: -9999px; top: -9999px;",
765
+ "position: fixed; left: -100vw; visibility: hidden;",
766
+ "opacity: 0; height: 0; width: 0; overflow: hidden;",
767
+ "clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;",
768
+ "transform: scale(0); position: absolute;"
769
+ ];
770
+ return styles[Math.floor(Math.random() * styles.length)];
771
+ }
772
+ function humanizeFieldName(field) {
773
+ return field.replace(/^_hp_/, "").replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
774
+ }
775
+
776
+ // src/middleware/bot/behavior.ts
777
+ var DEFAULT_BEHAVIOR_OPTIONS = {
778
+ minRequestInterval: 100,
779
+ maxRequestsPerSecond: 10,
780
+ windowMs: 6e4,
781
+ store: void 0,
782
+ identifier: void 0,
783
+ patterns: {
784
+ sequentialAccess: true,
785
+ regularTiming: true,
786
+ missingHeaders: true
787
+ }
788
+ };
789
+ var MemoryBehaviorStore = class {
790
+ records = /* @__PURE__ */ new Map();
791
+ maxIdentifiers;
792
+ accessOrder = [];
793
+ constructor(options = {}) {
794
+ this.maxIdentifiers = options.maxIdentifiers || 1e4;
795
+ }
796
+ async record(identifier, timestamp, path) {
797
+ if (!this.records.has(identifier) && this.records.size >= this.maxIdentifiers) {
798
+ const oldest = this.accessOrder.shift();
799
+ if (oldest) {
800
+ this.records.delete(oldest);
801
+ }
802
+ }
803
+ const idx = this.accessOrder.indexOf(identifier);
804
+ if (idx > -1) {
805
+ this.accessOrder.splice(idx, 1);
806
+ }
807
+ this.accessOrder.push(identifier);
808
+ const records = this.records.get(identifier) || [];
809
+ records.push({ timestamp, path });
810
+ this.records.set(identifier, records);
811
+ }
812
+ async getHistory(identifier, windowMs) {
813
+ const records = this.records.get(identifier) || [];
814
+ const cutoff = Date.now() - windowMs;
815
+ const filtered = records.filter((r) => r.timestamp > cutoff);
816
+ if (filtered.length !== records.length) {
817
+ this.records.set(identifier, filtered);
818
+ }
819
+ return filtered;
820
+ }
821
+ async cleanup(maxAge) {
822
+ const cutoff = Date.now() - maxAge;
823
+ for (const [identifier, records] of this.records.entries()) {
824
+ const filtered = records.filter((r) => r.timestamp > cutoff);
825
+ if (filtered.length === 0) {
826
+ this.records.delete(identifier);
827
+ const idx = this.accessOrder.indexOf(identifier);
828
+ if (idx > -1) {
829
+ this.accessOrder.splice(idx, 1);
830
+ }
831
+ } else {
832
+ this.records.set(identifier, filtered);
833
+ }
834
+ }
835
+ }
836
+ /**
837
+ * Get store statistics
838
+ */
839
+ getStats() {
840
+ let totalRecords = 0;
841
+ for (const records of this.records.values()) {
842
+ totalRecords += records.length;
843
+ }
844
+ return {
845
+ identifiers: this.records.size,
846
+ totalRecords
847
+ };
848
+ }
849
+ /**
850
+ * Clear all records
851
+ */
852
+ clear() {
853
+ this.records.clear();
854
+ this.accessOrder = [];
855
+ }
856
+ };
857
+ async function analyzeBehavior(req, history, options = {}) {
858
+ const {
859
+ minRequestInterval = DEFAULT_BEHAVIOR_OPTIONS.minRequestInterval,
860
+ maxRequestsPerSecond = DEFAULT_BEHAVIOR_OPTIONS.maxRequestsPerSecond,
861
+ patterns = DEFAULT_BEHAVIOR_OPTIONS.patterns
862
+ } = options;
863
+ const reasons = [];
864
+ let score = 0;
865
+ const now = Date.now();
866
+ const requestCount = history.length;
867
+ const intervals = [];
868
+ for (let i = 1; i < history.length; i++) {
869
+ intervals.push(history[i].timestamp - history[i - 1].timestamp);
870
+ }
871
+ const avgInterval = intervals.length > 0 ? intervals.reduce((a, b) => a + b, 0) / intervals.length : Infinity;
872
+ const oneSecondAgo = now - 1e3;
873
+ const requestsLastSecond = history.filter((r) => r.timestamp > oneSecondAgo).length;
874
+ if (requestsLastSecond > maxRequestsPerSecond) {
875
+ score += 0.3;
876
+ reasons.push(`High request rate: ${requestsLastSecond}/s (max: ${maxRequestsPerSecond})`);
877
+ }
878
+ const hasRapidRequests = intervals.some((i) => i < minRequestInterval);
879
+ if (hasRapidRequests) {
880
+ score += 0.25;
881
+ reasons.push(`Rapid requests detected: interval < ${minRequestInterval}ms`);
882
+ }
883
+ if (patterns?.regularTiming && intervals.length >= 5) {
884
+ const variance = calculateVariance(intervals);
885
+ const coefficientOfVariation = Math.sqrt(variance) / avgInterval;
886
+ if (coefficientOfVariation < 0.1) {
887
+ score += 0.2;
888
+ reasons.push("Suspiciously regular request timing");
889
+ }
890
+ }
891
+ if (patterns?.sequentialAccess && history.length >= 5) {
892
+ const paths = history.map((r) => r.path);
893
+ if (isSequentialPattern(paths)) {
894
+ score += 0.15;
895
+ reasons.push("Sequential URL access pattern detected");
896
+ }
897
+ }
898
+ if (patterns?.missingHeaders) {
899
+ const missingScore = checkMissingHeaders(req);
900
+ if (missingScore > 0) {
901
+ score += missingScore;
902
+ reasons.push("Missing typical browser headers");
903
+ }
904
+ }
905
+ score = Math.min(1, score);
906
+ return {
907
+ suspicious: score >= 0.5,
908
+ score,
909
+ reasons,
910
+ requestCount,
911
+ avgInterval
912
+ };
913
+ }
914
+ async function checkBehavior(req, options = {}) {
915
+ const {
916
+ store = new MemoryBehaviorStore(),
917
+ windowMs = DEFAULT_BEHAVIOR_OPTIONS.windowMs,
918
+ identifier: getIdentifier
919
+ } = options;
920
+ const identifier = getIdentifier ? await getIdentifier(req) : getClientIP2(req);
921
+ const history = await store.getHistory(identifier, windowMs);
922
+ const now = Date.now();
923
+ const path = new URL(req.url).pathname;
924
+ await store.record(identifier, now, path);
925
+ const updatedHistory = [...history, { timestamp: now, path }];
926
+ const analysis = await analyzeBehavior(req, updatedHistory, options);
927
+ return {
928
+ isBot: analysis.suspicious,
929
+ confidence: analysis.score,
930
+ reason: analysis.reasons.join("; ") || "Behavior analysis passed",
931
+ ip: identifier
932
+ };
933
+ }
934
+ var globalBehaviorStore;
935
+ function getGlobalBehaviorStore() {
936
+ if (!globalBehaviorStore) {
937
+ globalBehaviorStore = new MemoryBehaviorStore();
938
+ }
939
+ return globalBehaviorStore;
940
+ }
941
+ function withBehaviorAnalysis(handler, options = {}) {
942
+ const store = options.store || getGlobalBehaviorStore();
943
+ const mergedOptions = { ...options, store };
944
+ return async (req, ctx) => {
945
+ const result = await checkBehavior(req, mergedOptions);
946
+ if (result.isBot && result.confidence >= 0.5) {
947
+ return new Response(
948
+ JSON.stringify({
949
+ error: "Too Many Requests",
950
+ message: "Unusual request pattern detected",
951
+ retryAfter: 60
952
+ }),
953
+ {
954
+ status: 429,
955
+ headers: {
956
+ "Content-Type": "application/json",
957
+ "Retry-After": "60"
958
+ }
959
+ }
960
+ );
961
+ }
962
+ return handler(req, ctx);
963
+ };
964
+ }
965
+ function getClientIP2(req) {
966
+ return req.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || req.headers.get("x-real-ip") || req.headers.get("cf-connecting-ip") || "unknown";
967
+ }
968
+ function calculateVariance(numbers) {
969
+ if (numbers.length === 0) return 0;
970
+ const mean = numbers.reduce((a, b) => a + b, 0) / numbers.length;
971
+ const squaredDiffs = numbers.map((n) => Math.pow(n - mean, 2));
972
+ return squaredDiffs.reduce((a, b) => a + b, 0) / numbers.length;
973
+ }
974
+ function isSequentialPattern(paths) {
975
+ const numbers = paths.map((p) => {
976
+ const match = p.match(/(\d+)/);
977
+ return match ? parseInt(match[1], 10) : null;
978
+ }).filter((n) => n !== null);
979
+ if (numbers.length < 3) return false;
980
+ let sequential = 0;
981
+ for (let i = 1; i < numbers.length; i++) {
982
+ if (numbers[i] === numbers[i - 1] + 1) {
983
+ sequential++;
984
+ }
985
+ }
986
+ return sequential >= numbers.length * 0.6;
987
+ }
988
+ function checkMissingHeaders(req) {
989
+ let score = 0;
990
+ const typicalHeaders = [
991
+ "accept",
992
+ "accept-language",
993
+ "accept-encoding"
994
+ ];
995
+ for (const header of typicalHeaders) {
996
+ if (!req.headers.get(header)) {
997
+ score += 0.05;
998
+ }
999
+ }
1000
+ const accept = req.headers.get("accept");
1001
+ if (accept && !accept.includes("text/html") && !accept.includes("application/json") && !accept.includes("*/*")) {
1002
+ score += 0.05;
1003
+ }
1004
+ const referer = req.headers.get("referer");
1005
+ const path = new URL(req.url).pathname;
1006
+ if (!referer && path !== "/" && !path.includes("/api/")) {
1007
+ score += 0.03;
1008
+ }
1009
+ return score;
1010
+ }
1011
+
1012
+ // src/middleware/bot/captcha.ts
1013
+ var CAPTCHA_VERIFY_URLS = {
1014
+ recaptcha: "https://www.google.com/recaptcha/api/siteverify",
1015
+ hcaptcha: "https://hcaptcha.com/siteverify",
1016
+ turnstile: "https://challenges.cloudflare.com/turnstile/v0/siteverify"
1017
+ };
1018
+ var DEFAULT_TOKEN_FIELDS = {
1019
+ recaptcha: "g-recaptcha-response",
1020
+ hcaptcha: "h-captcha-response",
1021
+ turnstile: "cf-turnstile-response"
1022
+ };
1023
+ async function verifyCaptcha(token, options) {
1024
+ const { provider, secretKey, action } = options;
1025
+ const verifyUrl = CAPTCHA_VERIFY_URLS[provider];
1026
+ const formData = new URLSearchParams();
1027
+ formData.append("secret", secretKey);
1028
+ formData.append("response", token);
1029
+ try {
1030
+ const response = await fetch(verifyUrl, {
1031
+ method: "POST",
1032
+ headers: {
1033
+ "Content-Type": "application/x-www-form-urlencoded"
1034
+ },
1035
+ body: formData.toString()
1036
+ });
1037
+ if (!response.ok) {
1038
+ return {
1039
+ success: false,
1040
+ errorCodes: [`HTTP ${response.status}`]
1041
+ };
1042
+ }
1043
+ const data = await response.json();
1044
+ return parseCaptchaResponse(data, provider, action);
1045
+ } catch (error) {
1046
+ return {
1047
+ success: false,
1048
+ errorCodes: ["verification-failed", String(error)]
1049
+ };
1050
+ }
1051
+ }
1052
+ function parseCaptchaResponse(data, provider, expectedAction) {
1053
+ switch (provider) {
1054
+ case "recaptcha":
1055
+ return parseRecaptchaResponse(data, expectedAction);
1056
+ case "hcaptcha":
1057
+ return parseHCaptchaResponse(data);
1058
+ case "turnstile":
1059
+ return parseTurnstileResponse(data);
1060
+ default:
1061
+ return {
1062
+ success: false,
1063
+ errorCodes: ["unknown-provider"]
1064
+ };
1065
+ }
1066
+ }
1067
+ function parseRecaptchaResponse(data, expectedAction) {
1068
+ const result = {
1069
+ success: data.success === true,
1070
+ score: typeof data.score === "number" ? data.score : void 0,
1071
+ action: typeof data.action === "string" ? data.action : void 0,
1072
+ hostname: typeof data.hostname === "string" ? data.hostname : void 0,
1073
+ challengeTs: typeof data.challenge_ts === "string" ? data.challenge_ts : void 0,
1074
+ errorCodes: Array.isArray(data["error-codes"]) ? data["error-codes"] : void 0
1075
+ };
1076
+ if (result.success && expectedAction && result.action !== expectedAction) {
1077
+ result.success = false;
1078
+ result.errorCodes = ["action-mismatch"];
1079
+ }
1080
+ return result;
1081
+ }
1082
+ function parseHCaptchaResponse(data) {
1083
+ return {
1084
+ success: data.success === true,
1085
+ hostname: typeof data.hostname === "string" ? data.hostname : void 0,
1086
+ challengeTs: typeof data.challenge_ts === "string" ? data.challenge_ts : void 0,
1087
+ errorCodes: Array.isArray(data["error-codes"]) ? data["error-codes"] : void 0
1088
+ };
1089
+ }
1090
+ function parseTurnstileResponse(data) {
1091
+ return {
1092
+ success: data.success === true,
1093
+ hostname: typeof data.hostname === "string" ? data.hostname : void 0,
1094
+ challengeTs: typeof data.challenge_ts === "string" ? data.challenge_ts : void 0,
1095
+ action: typeof data.action === "string" ? data.action : void 0,
1096
+ errorCodes: Array.isArray(data["error-codes"]) ? data["error-codes"] : void 0
1097
+ };
1098
+ }
1099
+ async function extractCaptchaToken(req, options) {
1100
+ const { provider, tokenField } = options;
1101
+ const fieldName = tokenField || DEFAULT_TOKEN_FIELDS[provider];
1102
+ const url = new URL(req.url);
1103
+ const queryToken = url.searchParams.get(fieldName);
1104
+ if (queryToken) {
1105
+ return queryToken;
1106
+ }
1107
+ if (hasBody2(req)) {
1108
+ try {
1109
+ const body = await getRequestBody2(req);
1110
+ if (body && typeof body === "object") {
1111
+ const bodyToken = body[fieldName];
1112
+ if (typeof bodyToken === "string") {
1113
+ return bodyToken;
1114
+ }
1115
+ }
1116
+ } catch {
1117
+ }
1118
+ }
1119
+ const headerToken = req.headers.get(`x-${fieldName}`);
1120
+ if (headerToken) {
1121
+ return headerToken;
1122
+ }
1123
+ return null;
1124
+ }
1125
+ async function checkCaptcha(req, options) {
1126
+ const { threshold = 0.5, skip } = options;
1127
+ if (skip && await skip(req)) {
1128
+ return {
1129
+ isBot: false,
1130
+ confidence: 0,
1131
+ reason: "CAPTCHA check skipped"
1132
+ };
1133
+ }
1134
+ const token = await extractCaptchaToken(req, options);
1135
+ if (!token) {
1136
+ return {
1137
+ isBot: true,
1138
+ confidence: 0.9,
1139
+ reason: "CAPTCHA token missing",
1140
+ ip: getClientIP3(req)
1141
+ };
1142
+ }
1143
+ const result = await verifyCaptcha(token, options);
1144
+ if (!result.success) {
1145
+ return {
1146
+ isBot: true,
1147
+ confidence: 0.95,
1148
+ reason: `CAPTCHA verification failed: ${result.errorCodes?.join(", ") || "unknown"}`,
1149
+ ip: getClientIP3(req)
1150
+ };
1151
+ }
1152
+ if (result.score !== void 0 && result.score < threshold) {
1153
+ return {
1154
+ isBot: true,
1155
+ confidence: 1 - result.score,
1156
+ reason: `CAPTCHA score too low: ${result.score} (threshold: ${threshold})`,
1157
+ ip: getClientIP3(req)
1158
+ };
1159
+ }
1160
+ return {
1161
+ isBot: false,
1162
+ confidence: result.score !== void 0 ? 1 - result.score : 0.1,
1163
+ reason: "CAPTCHA verification passed"
1164
+ };
1165
+ }
1166
+ function withCaptcha(handler, options) {
1167
+ return async (req, ctx) => {
1168
+ const result = await checkCaptcha(req, options);
1169
+ if (result.isBot) {
1170
+ return new Response(
1171
+ JSON.stringify({
1172
+ error: "CAPTCHA Required",
1173
+ message: result.reason,
1174
+ code: "CAPTCHA_FAILED"
1175
+ }),
1176
+ {
1177
+ status: 403,
1178
+ headers: { "Content-Type": "application/json" }
1179
+ }
1180
+ );
1181
+ }
1182
+ return handler(req, ctx);
1183
+ };
1184
+ }
1185
+ function generateRecaptchaV2(siteKey, options = {}) {
1186
+ const { theme = "light", size = "normal" } = options;
1187
+ return `
1188
+ <script src="https://www.google.com/recaptcha/api.js" async defer></script>
1189
+ <div class="g-recaptcha" data-sitekey="${siteKey}" data-theme="${theme}" data-size="${size}"></div>
1190
+ `.trim();
1191
+ }
1192
+ function generateRecaptchaV3(siteKey, action = "submit") {
1193
+ return `
1194
+ <script src="https://www.google.com/recaptcha/api.js?render=${siteKey}"></script>
1195
+ <script>
1196
+ function getRecaptchaToken() {
1197
+ return new Promise((resolve, reject) => {
1198
+ grecaptcha.ready(() => {
1199
+ grecaptcha.execute('${siteKey}', { action: '${action}' })
1200
+ .then(resolve)
1201
+ .catch(reject);
1202
+ });
1203
+ });
1204
+ }
1205
+ </script>
1206
+ `.trim();
1207
+ }
1208
+ function generateHCaptcha(siteKey, options = {}) {
1209
+ const { theme = "light", size = "normal" } = options;
1210
+ return `
1211
+ <script src="https://js.hcaptcha.com/1/api.js" async defer></script>
1212
+ <div class="h-captcha" data-sitekey="${siteKey}" data-theme="${theme}" data-size="${size}"></div>
1213
+ `.trim();
1214
+ }
1215
+ function generateTurnstile(siteKey, options = {}) {
1216
+ const { theme = "auto", size = "normal" } = options;
1217
+ return `
1218
+ <script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
1219
+ <div class="cf-turnstile" data-sitekey="${siteKey}" data-theme="${theme}" data-size="${size}"></div>
1220
+ `.trim();
1221
+ }
1222
+ function hasBody2(req) {
1223
+ const method = req.method.toUpperCase();
1224
+ return ["POST", "PUT", "PATCH", "DELETE"].includes(method);
1225
+ }
1226
+ async function getRequestBody2(req) {
1227
+ try {
1228
+ const contentType = req.headers.get("content-type") || "";
1229
+ if (contentType.includes("application/json")) {
1230
+ const cloned = req.clone();
1231
+ return await cloned.json();
1232
+ }
1233
+ if (contentType.includes("application/x-www-form-urlencoded")) {
1234
+ const cloned = req.clone();
1235
+ const text = await cloned.text();
1236
+ return Object.fromEntries(new URLSearchParams(text));
1237
+ }
1238
+ if (contentType.includes("multipart/form-data")) {
1239
+ const cloned = req.clone();
1240
+ const formData = await cloned.formData();
1241
+ const obj = {};
1242
+ formData.forEach((value, key) => {
1243
+ obj[key] = value;
1244
+ });
1245
+ return obj;
1246
+ }
1247
+ return null;
1248
+ } catch {
1249
+ return null;
1250
+ }
1251
+ }
1252
+ function getClientIP3(req) {
1253
+ return req.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || req.headers.get("x-real-ip") || req.headers.get("cf-connecting-ip") || "unknown";
1254
+ }
1255
+
1256
+ // src/middleware/bot/middleware.ts
1257
+ function defaultBotResponse(result) {
1258
+ return new Response(
1259
+ JSON.stringify({
1260
+ error: "Forbidden",
1261
+ message: result.reason || "Request blocked",
1262
+ code: "BOT_DETECTED",
1263
+ category: result.category
1264
+ }),
1265
+ {
1266
+ status: 403,
1267
+ headers: {
1268
+ "Content-Type": "application/json",
1269
+ "X-Bot-Detection": "true"
1270
+ }
1271
+ }
1272
+ );
1273
+ }
1274
+ async function detectBot(req, options = {}) {
1275
+ const results = [];
1276
+ if (options.userAgent !== false) {
1277
+ const uaOptions = options.userAgent === true ? {} : options.userAgent || {};
1278
+ const userAgent = req.headers.get("user-agent");
1279
+ const uaResult = analyzeUserAgent(userAgent, uaOptions);
1280
+ if (uaResult.isBot && !isAllowedBot(uaResult, uaOptions)) {
1281
+ results.push(uaResult);
1282
+ }
1283
+ }
1284
+ if (options.honeypot !== false && hasBody3(req)) {
1285
+ const hpOptions = options.honeypot === true ? {} : options.honeypot || {};
1286
+ const hpResult = await checkHoneypot(req, hpOptions);
1287
+ if (hpResult.isBot) {
1288
+ results.push(hpResult);
1289
+ }
1290
+ }
1291
+ if (options.behavior !== false) {
1292
+ const behaviorOptions = options.behavior === true ? {} : options.behavior || {};
1293
+ if (!behaviorOptions.store) {
1294
+ behaviorOptions.store = getGlobalBehaviorStore();
1295
+ }
1296
+ const behaviorResult = await checkBehavior(req, behaviorOptions);
1297
+ if (behaviorResult.isBot) {
1298
+ results.push(behaviorResult);
1299
+ }
1300
+ }
1301
+ if (options.captcha) {
1302
+ const captchaResult = await checkCaptcha(req, options.captcha);
1303
+ if (captchaResult.isBot) {
1304
+ results.push(captchaResult);
1305
+ }
1306
+ }
1307
+ if (results.length === 0) {
1308
+ return {
1309
+ isBot: false,
1310
+ confidence: 0,
1311
+ reason: "All checks passed",
1312
+ ip: getClientIP4(req),
1313
+ userAgent: req.headers.get("user-agent") || void 0
1314
+ };
1315
+ }
1316
+ const highestConfidence = results.reduce(
1317
+ (prev, curr) => curr.confidence > prev.confidence ? curr : prev
1318
+ );
1319
+ const allReasons = results.map((r) => r.reason).filter(Boolean).join("; ");
1320
+ return {
1321
+ isBot: true,
1322
+ category: highestConfidence.category,
1323
+ name: highestConfidence.name,
1324
+ confidence: highestConfidence.confidence,
1325
+ reason: allReasons,
1326
+ ip: getClientIP4(req),
1327
+ userAgent: req.headers.get("user-agent") || void 0
1328
+ };
1329
+ }
1330
+ function isAllowedBot(result, options) {
1331
+ const {
1332
+ allowCategories = DEFAULT_ALLOWED_CATEGORIES,
1333
+ allowList = DEFAULT_ALLOWED_BOTS,
1334
+ blockList = [],
1335
+ blockAllBots = false
1336
+ } = options;
1337
+ if (result.name && blockList.includes(result.name)) {
1338
+ return false;
1339
+ }
1340
+ if (blockAllBots) {
1341
+ return false;
1342
+ }
1343
+ if (result.name && allowList.includes(result.name)) {
1344
+ return true;
1345
+ }
1346
+ if (result.category && allowCategories.includes(result.category)) {
1347
+ return true;
1348
+ }
1349
+ return false;
1350
+ }
1351
+ function withBotProtection(handler, options = {}) {
1352
+ const {
1353
+ skip,
1354
+ onBot,
1355
+ log,
1356
+ mode = "block"
1357
+ } = options;
1358
+ return async (req, ctx) => {
1359
+ if (skip && await skip(req)) {
1360
+ return handler(req, { ...ctx, bot: void 0 });
1361
+ }
1362
+ const result = await detectBot(req, options);
1363
+ if (log) {
1364
+ if (typeof log === "function") {
1365
+ log(result);
1366
+ } else if (result.isBot) {
1367
+ console.log("[Bot Detection]", JSON.stringify(result));
1368
+ }
1369
+ }
1370
+ if (result.isBot) {
1371
+ if (mode === "block") {
1372
+ if (onBot) {
1373
+ return onBot(req, result);
1374
+ }
1375
+ return defaultBotResponse(result);
1376
+ }
1377
+ }
1378
+ const extendedCtx = {
1379
+ ...ctx,
1380
+ bot: result
1381
+ };
1382
+ return handler(req, extendedCtx);
1383
+ };
1384
+ }
1385
+ function withUserAgentProtection(handler, options = {}) {
1386
+ return withBotProtection(handler, {
1387
+ userAgent: options,
1388
+ honeypot: false,
1389
+ behavior: false
1390
+ });
1391
+ }
1392
+ function withHoneypotProtection(handler, options = {}) {
1393
+ return withBotProtection(handler, {
1394
+ userAgent: false,
1395
+ honeypot: options,
1396
+ behavior: false
1397
+ });
1398
+ }
1399
+ function withBehaviorProtection(handler, options = {}) {
1400
+ return withBotProtection(handler, {
1401
+ userAgent: false,
1402
+ honeypot: false,
1403
+ behavior: options
1404
+ });
1405
+ }
1406
+ function withCaptchaProtection(handler, options) {
1407
+ return withBotProtection(handler, {
1408
+ userAgent: false,
1409
+ honeypot: false,
1410
+ behavior: false,
1411
+ captcha: options
1412
+ });
1413
+ }
1414
+ var BOT_PROTECTION_PRESETS = {
1415
+ /**
1416
+ * Relaxed - Only blocks obvious bots
1417
+ */
1418
+ relaxed: {
1419
+ userAgent: {
1420
+ blockAllBots: false,
1421
+ allowCategories: ["search_engine", "social_media", "monitoring", "feed_reader", "preview", "seo"]
1422
+ },
1423
+ honeypot: false,
1424
+ behavior: false
1425
+ },
1426
+ /**
1427
+ * Standard - Good balance of protection
1428
+ */
1429
+ standard: {
1430
+ userAgent: {
1431
+ blockAllBots: false,
1432
+ allowCategories: ["search_engine", "social_media", "monitoring"]
1433
+ },
1434
+ honeypot: true,
1435
+ behavior: {
1436
+ maxRequestsPerSecond: 10
1437
+ }
1438
+ },
1439
+ /**
1440
+ * Strict - Maximum protection
1441
+ */
1442
+ strict: {
1443
+ userAgent: {
1444
+ blockAllBots: false,
1445
+ allowCategories: ["search_engine"],
1446
+ blockEmptyUA: true,
1447
+ blockSuspiciousUA: true
1448
+ },
1449
+ honeypot: {
1450
+ additionalFields: ["_hp_name", "_hp_phone"]
1451
+ },
1452
+ behavior: {
1453
+ maxRequestsPerSecond: 5,
1454
+ minRequestInterval: 200
1455
+ }
1456
+ },
1457
+ /**
1458
+ * API - For API endpoints
1459
+ */
1460
+ api: {
1461
+ userAgent: {
1462
+ blockAllBots: true,
1463
+ blockEmptyUA: true
1464
+ },
1465
+ honeypot: false,
1466
+ behavior: {
1467
+ maxRequestsPerSecond: 20
1468
+ }
1469
+ }
1470
+ };
1471
+ function withBotProtectionPreset(handler, preset, overrides = {}) {
1472
+ const presetOptions = BOT_PROTECTION_PRESETS[preset];
1473
+ const mergedOptions = { ...presetOptions, ...overrides };
1474
+ return withBotProtection(handler, mergedOptions);
1475
+ }
1476
+ function hasBody3(req) {
1477
+ const method = req.method.toUpperCase();
1478
+ return ["POST", "PUT", "PATCH", "DELETE"].includes(method);
1479
+ }
1480
+ function getClientIP4(req) {
1481
+ return req.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || req.headers.get("x-real-ip") || req.headers.get("cf-connecting-ip") || "unknown";
1482
+ }
1483
+
1484
+ exports.BOT_PROTECTION_PRESETS = BOT_PROTECTION_PRESETS;
1485
+ exports.DEFAULT_ALLOWED_BOTS = DEFAULT_ALLOWED_BOTS;
1486
+ exports.DEFAULT_ALLOWED_CATEGORIES = DEFAULT_ALLOWED_CATEGORIES;
1487
+ exports.DEFAULT_BEHAVIOR_OPTIONS = DEFAULT_BEHAVIOR_OPTIONS;
1488
+ exports.DEFAULT_HONEYPOT_FIELDS = DEFAULT_HONEYPOT_FIELDS;
1489
+ exports.DEFAULT_HONEYPOT_OPTIONS = DEFAULT_HONEYPOT_OPTIONS;
1490
+ exports.KNOWN_BOT_PATTERNS = KNOWN_BOT_PATTERNS;
1491
+ exports.MemoryBehaviorStore = MemoryBehaviorStore;
1492
+ exports.analyzeBehavior = analyzeBehavior;
1493
+ exports.analyzeUserAgent = analyzeUserAgent;
1494
+ exports.checkBehavior = checkBehavior;
1495
+ exports.checkCaptcha = checkCaptcha;
1496
+ exports.checkHoneypot = checkHoneypot;
1497
+ exports.createBotPattern = createBotPattern;
1498
+ exports.detectBot = detectBot;
1499
+ exports.extractCaptchaToken = extractCaptchaToken;
1500
+ exports.generateHCaptcha = generateHCaptcha;
1501
+ exports.generateHoneypotCSS = generateHoneypotCSS;
1502
+ exports.generateHoneypotHTML = generateHoneypotHTML;
1503
+ exports.generateRecaptchaV2 = generateRecaptchaV2;
1504
+ exports.generateRecaptchaV3 = generateRecaptchaV3;
1505
+ exports.generateTurnstile = generateTurnstile;
1506
+ exports.getBotsByCategory = getBotsByCategory;
1507
+ exports.getGlobalBehaviorStore = getGlobalBehaviorStore;
1508
+ exports.isBotAllowed = isBotAllowed;
1509
+ exports.isSuspiciousUA = isSuspiciousUA;
1510
+ exports.verifyCaptcha = verifyCaptcha;
1511
+ exports.withBehaviorAnalysis = withBehaviorAnalysis;
1512
+ exports.withBehaviorProtection = withBehaviorProtection;
1513
+ exports.withBotProtection = withBotProtection;
1514
+ exports.withBotProtectionPreset = withBotProtectionPreset;
1515
+ exports.withCaptcha = withCaptcha;
1516
+ exports.withCaptchaProtection = withCaptchaProtection;
1517
+ exports.withHoneypot = withHoneypot;
1518
+ exports.withHoneypotProtection = withHoneypotProtection;
1519
+ exports.withUserAgentProtection = withUserAgentProtection;
1520
+ //# sourceMappingURL=bot.cjs.map
1521
+ //# sourceMappingURL=bot.cjs.map