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/index.js CHANGED
@@ -5105,9 +5105,1486 @@ function withTiming(handler, options = {}) {
5105
5105
  };
5106
5106
  }
5107
5107
 
5108
+ // src/middleware/bot/user-agent.ts
5109
+ var KNOWN_BOT_PATTERNS = [
5110
+ // Search Engines - Generally allowed
5111
+ {
5112
+ name: "Googlebot",
5113
+ pattern: /Googlebot|Google-InspectionTool|Storebot-Google|GoogleOther/i,
5114
+ category: "search_engine",
5115
+ allowed: true,
5116
+ description: "Google search crawler"
5117
+ },
5118
+ {
5119
+ name: "Bingbot",
5120
+ pattern: /bingbot|msnbot|BingPreview/i,
5121
+ category: "search_engine",
5122
+ allowed: true,
5123
+ description: "Microsoft Bing crawler"
5124
+ },
5125
+ {
5126
+ name: "Yahoo Slurp",
5127
+ pattern: /Slurp/i,
5128
+ category: "search_engine",
5129
+ allowed: true,
5130
+ description: "Yahoo search crawler"
5131
+ },
5132
+ {
5133
+ name: "DuckDuckBot",
5134
+ pattern: /DuckDuckBot|DuckDuckGo-Favicons-Bot/i,
5135
+ category: "search_engine",
5136
+ allowed: true,
5137
+ description: "DuckDuckGo crawler"
5138
+ },
5139
+ {
5140
+ name: "Baiduspider",
5141
+ pattern: /Baiduspider/i,
5142
+ category: "search_engine",
5143
+ allowed: true,
5144
+ description: "Baidu search crawler"
5145
+ },
5146
+ {
5147
+ name: "Yandex",
5148
+ pattern: /YandexBot|YandexImages|YandexMobileBot/i,
5149
+ category: "search_engine",
5150
+ allowed: true,
5151
+ description: "Yandex search crawler"
5152
+ },
5153
+ {
5154
+ name: "Applebot",
5155
+ pattern: /Applebot/i,
5156
+ category: "search_engine",
5157
+ allowed: true,
5158
+ description: "Apple search crawler"
5159
+ },
5160
+ {
5161
+ name: "Sogou",
5162
+ pattern: /Sogou/i,
5163
+ category: "search_engine",
5164
+ allowed: true,
5165
+ description: "Sogou search crawler"
5166
+ },
5167
+ {
5168
+ name: "Exabot",
5169
+ pattern: /Exabot/i,
5170
+ category: "search_engine",
5171
+ allowed: true,
5172
+ description: "Exalead search crawler"
5173
+ },
5174
+ // Social Media - Generally allowed
5175
+ {
5176
+ name: "Facebook",
5177
+ pattern: /facebookexternalhit|Facebot|facebookcatalog/i,
5178
+ category: "social_media",
5179
+ allowed: true,
5180
+ description: "Facebook crawler for link previews"
5181
+ },
5182
+ {
5183
+ name: "Twitter",
5184
+ pattern: /Twitterbot/i,
5185
+ category: "social_media",
5186
+ allowed: true,
5187
+ description: "Twitter/X crawler for cards"
5188
+ },
5189
+ {
5190
+ name: "LinkedIn",
5191
+ pattern: /LinkedInBot/i,
5192
+ category: "social_media",
5193
+ allowed: true,
5194
+ description: "LinkedIn crawler for previews"
5195
+ },
5196
+ {
5197
+ name: "Pinterest",
5198
+ pattern: /Pinterest|Pinterestbot/i,
5199
+ category: "social_media",
5200
+ allowed: true,
5201
+ description: "Pinterest crawler"
5202
+ },
5203
+ {
5204
+ name: "Slack",
5205
+ pattern: /Slackbot/i,
5206
+ category: "social_media",
5207
+ allowed: true,
5208
+ description: "Slack link preview bot"
5209
+ },
5210
+ {
5211
+ name: "Discord",
5212
+ pattern: /Discordbot/i,
5213
+ category: "social_media",
5214
+ allowed: true,
5215
+ description: "Discord link preview bot"
5216
+ },
5217
+ {
5218
+ name: "Telegram",
5219
+ pattern: /TelegramBot/i,
5220
+ category: "social_media",
5221
+ allowed: true,
5222
+ description: "Telegram link preview bot"
5223
+ },
5224
+ {
5225
+ name: "WhatsApp",
5226
+ pattern: /WhatsApp/i,
5227
+ category: "social_media",
5228
+ allowed: true,
5229
+ description: "WhatsApp link preview"
5230
+ },
5231
+ {
5232
+ name: "Snapchat",
5233
+ pattern: /Snapchat/i,
5234
+ category: "social_media",
5235
+ allowed: true,
5236
+ description: "Snapchat crawler"
5237
+ },
5238
+ // Monitoring - Generally allowed
5239
+ {
5240
+ name: "UptimeRobot",
5241
+ pattern: /UptimeRobot/i,
5242
+ category: "monitoring",
5243
+ allowed: true,
5244
+ description: "UptimeRobot monitoring"
5245
+ },
5246
+ {
5247
+ name: "Pingdom",
5248
+ pattern: /Pingdom/i,
5249
+ category: "monitoring",
5250
+ allowed: true,
5251
+ description: "Pingdom monitoring"
5252
+ },
5253
+ {
5254
+ name: "StatusCake",
5255
+ pattern: /StatusCake/i,
5256
+ category: "monitoring",
5257
+ allowed: true,
5258
+ description: "StatusCake monitoring"
5259
+ },
5260
+ {
5261
+ name: "Site24x7",
5262
+ pattern: /Site24x7/i,
5263
+ category: "monitoring",
5264
+ allowed: true,
5265
+ description: "Site24x7 monitoring"
5266
+ },
5267
+ {
5268
+ name: "Datadog",
5269
+ pattern: /Datadog/i,
5270
+ category: "monitoring",
5271
+ allowed: true,
5272
+ description: "Datadog synthetic monitoring"
5273
+ },
5274
+ {
5275
+ name: "New Relic",
5276
+ pattern: /NewRelicPinger/i,
5277
+ category: "monitoring",
5278
+ allowed: true,
5279
+ description: "New Relic synthetic monitoring"
5280
+ },
5281
+ {
5282
+ name: "Checkly",
5283
+ pattern: /Checkly/i,
5284
+ category: "monitoring",
5285
+ allowed: true,
5286
+ description: "Checkly monitoring"
5287
+ },
5288
+ // SEO Tools - Usually allowed but can be blocked
5289
+ {
5290
+ name: "Ahrefs",
5291
+ pattern: /AhrefsBot|AhrefsSiteAudit/i,
5292
+ category: "seo",
5293
+ allowed: false,
5294
+ description: "Ahrefs SEO crawler"
5295
+ },
5296
+ {
5297
+ name: "Semrush",
5298
+ pattern: /SemrushBot/i,
5299
+ category: "seo",
5300
+ allowed: false,
5301
+ description: "Semrush SEO crawler"
5302
+ },
5303
+ {
5304
+ name: "Moz",
5305
+ pattern: /rogerbot|DotBot/i,
5306
+ category: "seo",
5307
+ allowed: false,
5308
+ description: "Moz SEO crawler"
5309
+ },
5310
+ {
5311
+ name: "Majestic",
5312
+ pattern: /MJ12bot/i,
5313
+ category: "seo",
5314
+ allowed: false,
5315
+ description: "Majestic SEO crawler"
5316
+ },
5317
+ {
5318
+ name: "Screaming Frog",
5319
+ pattern: /Screaming Frog/i,
5320
+ category: "seo",
5321
+ allowed: false,
5322
+ description: "Screaming Frog SEO Spider"
5323
+ },
5324
+ // AI Crawlers - Configurable
5325
+ {
5326
+ name: "GPTBot",
5327
+ pattern: /GPTBot/i,
5328
+ category: "ai_crawler",
5329
+ allowed: false,
5330
+ description: "OpenAI GPT training crawler"
5331
+ },
5332
+ {
5333
+ name: "ChatGPT-User",
5334
+ pattern: /ChatGPT-User/i,
5335
+ category: "ai_crawler",
5336
+ allowed: false,
5337
+ description: "ChatGPT user browsing"
5338
+ },
5339
+ {
5340
+ name: "Claude-Web",
5341
+ pattern: /Claude-Web|ClaudeBot|anthropic-ai/i,
5342
+ category: "ai_crawler",
5343
+ allowed: false,
5344
+ description: "Anthropic Claude crawler"
5345
+ },
5346
+ {
5347
+ name: "Bytespider",
5348
+ pattern: /Bytespider/i,
5349
+ category: "ai_crawler",
5350
+ allowed: false,
5351
+ description: "ByteDance AI crawler"
5352
+ },
5353
+ {
5354
+ name: "CCBot",
5355
+ pattern: /CCBot/i,
5356
+ category: "ai_crawler",
5357
+ allowed: false,
5358
+ description: "Common Crawl bot"
5359
+ },
5360
+ {
5361
+ name: "Google-Extended",
5362
+ pattern: /Google-Extended/i,
5363
+ category: "ai_crawler",
5364
+ allowed: false,
5365
+ description: "Google AI training crawler"
5366
+ },
5367
+ {
5368
+ name: "Cohere-ai",
5369
+ pattern: /cohere-ai/i,
5370
+ category: "ai_crawler",
5371
+ allowed: false,
5372
+ description: "Cohere AI crawler"
5373
+ },
5374
+ {
5375
+ name: "PerplexityBot",
5376
+ pattern: /PerplexityBot/i,
5377
+ category: "ai_crawler",
5378
+ allowed: false,
5379
+ description: "Perplexity AI crawler"
5380
+ },
5381
+ // Feed Readers - Generally allowed
5382
+ {
5383
+ name: "Feedly",
5384
+ pattern: /Feedly/i,
5385
+ category: "feed_reader",
5386
+ allowed: true,
5387
+ description: "Feedly RSS reader"
5388
+ },
5389
+ {
5390
+ name: "Feedbin",
5391
+ pattern: /Feedbin/i,
5392
+ category: "feed_reader",
5393
+ allowed: true,
5394
+ description: "Feedbin RSS reader"
5395
+ },
5396
+ {
5397
+ name: "NewsBlur",
5398
+ pattern: /NewsBlur/i,
5399
+ category: "feed_reader",
5400
+ allowed: true,
5401
+ description: "NewsBlur RSS reader"
5402
+ },
5403
+ // Link Previews - Generally allowed
5404
+ {
5405
+ name: "Embedly",
5406
+ pattern: /Embedly/i,
5407
+ category: "preview",
5408
+ allowed: true,
5409
+ description: "Embedly link preview"
5410
+ },
5411
+ {
5412
+ name: "Iframely",
5413
+ pattern: /Iframely/i,
5414
+ category: "preview",
5415
+ allowed: true,
5416
+ description: "Iframely link preview"
5417
+ },
5418
+ // Security Scanners - Block by default
5419
+ {
5420
+ name: "Nessus",
5421
+ pattern: /Nessus/i,
5422
+ category: "security",
5423
+ allowed: false,
5424
+ description: "Nessus vulnerability scanner"
5425
+ },
5426
+ {
5427
+ name: "Nikto",
5428
+ pattern: /Nikto/i,
5429
+ category: "security",
5430
+ allowed: false,
5431
+ description: "Nikto web scanner"
5432
+ },
5433
+ {
5434
+ name: "sqlmap",
5435
+ pattern: /sqlmap/i,
5436
+ category: "security",
5437
+ allowed: false,
5438
+ description: "sqlmap SQL injection tool"
5439
+ },
5440
+ {
5441
+ name: "WPScan",
5442
+ pattern: /WPScan/i,
5443
+ category: "security",
5444
+ allowed: false,
5445
+ description: "WordPress vulnerability scanner"
5446
+ },
5447
+ {
5448
+ name: "Acunetix",
5449
+ pattern: /Acunetix/i,
5450
+ category: "security",
5451
+ allowed: false,
5452
+ description: "Acunetix vulnerability scanner"
5453
+ },
5454
+ // Known Scrapers - Block
5455
+ {
5456
+ name: "Scrapy",
5457
+ pattern: /Scrapy/i,
5458
+ category: "scraper",
5459
+ allowed: false,
5460
+ description: "Scrapy web scraper"
5461
+ },
5462
+ {
5463
+ name: "HTTrack",
5464
+ pattern: /HTTrack/i,
5465
+ category: "scraper",
5466
+ allowed: false,
5467
+ description: "HTTrack website copier"
5468
+ },
5469
+ {
5470
+ name: "WebCopier",
5471
+ pattern: /WebCopier/i,
5472
+ category: "scraper",
5473
+ allowed: false,
5474
+ description: "WebCopier tool"
5475
+ },
5476
+ {
5477
+ name: "SiteSnagger",
5478
+ pattern: /SiteSnagger/i,
5479
+ category: "scraper",
5480
+ allowed: false,
5481
+ description: "SiteSnagger downloader"
5482
+ },
5483
+ // Spam Bots - Always block
5484
+ {
5485
+ name: "Spam Bot",
5486
+ pattern: /spam|harvester|extractor|collect/i,
5487
+ category: "spam",
5488
+ allowed: false,
5489
+ description: "Generic spam bot pattern"
5490
+ },
5491
+ {
5492
+ name: "Email Harvester",
5493
+ pattern: /email.*harvest|harvest.*email/i,
5494
+ category: "spam",
5495
+ allowed: false,
5496
+ description: "Email harvesting bot"
5497
+ },
5498
+ // Malicious - Always block
5499
+ {
5500
+ name: "Malicious Generic",
5501
+ pattern: /masscan|ZmEu|morfeus|nmap/i,
5502
+ category: "malicious",
5503
+ allowed: false,
5504
+ description: "Known malicious tools"
5505
+ },
5506
+ {
5507
+ name: "Vulnerability Scanner",
5508
+ pattern: /havij|w3af|webscarab/i,
5509
+ category: "malicious",
5510
+ allowed: false,
5511
+ description: "Vulnerability scanning tools"
5512
+ },
5513
+ // Generic Bot Pattern
5514
+ {
5515
+ name: "Generic Bot",
5516
+ pattern: /bot|crawl|spider|scrape|fetch/i,
5517
+ category: "unknown",
5518
+ allowed: false,
5519
+ description: "Generic bot pattern"
5520
+ },
5521
+ {
5522
+ name: "HTTP Library",
5523
+ pattern: /python-requests|python-urllib|curl|wget|axios|node-fetch|got\//i,
5524
+ category: "unknown",
5525
+ allowed: false,
5526
+ description: "HTTP library user agent"
5527
+ }
5528
+ ];
5529
+ var DEFAULT_ALLOWED_CATEGORIES = [
5530
+ "search_engine",
5531
+ "social_media",
5532
+ "monitoring",
5533
+ "feed_reader",
5534
+ "preview"
5535
+ ];
5536
+ var DEFAULT_ALLOWED_BOTS = [
5537
+ "Googlebot",
5538
+ "Bingbot",
5539
+ "Yahoo Slurp",
5540
+ "DuckDuckBot",
5541
+ "Applebot",
5542
+ "Facebook",
5543
+ "Twitter",
5544
+ "LinkedIn",
5545
+ "Slack",
5546
+ "Discord",
5547
+ "UptimeRobot",
5548
+ "Pingdom"
5549
+ ];
5550
+ function analyzeUserAgent(userAgent, options = {}) {
5551
+ const {
5552
+ blockAllBots = false,
5553
+ allowCategories = DEFAULT_ALLOWED_CATEGORIES,
5554
+ allowList = DEFAULT_ALLOWED_BOTS,
5555
+ blockList = [],
5556
+ customPatterns = [],
5557
+ blockEmptyUA = true,
5558
+ blockSuspiciousUA = true
5559
+ } = options;
5560
+ if (!userAgent || userAgent.trim() === "") {
5561
+ return {
5562
+ isBot: blockEmptyUA,
5563
+ category: "unknown",
5564
+ confidence: blockEmptyUA ? 0.9 : 0,
5565
+ reason: "Empty User-Agent",
5566
+ userAgent: userAgent || ""
5567
+ };
5568
+ }
5569
+ const allPatterns = [...customPatterns, ...KNOWN_BOT_PATTERNS];
5570
+ let matchedPattern = null;
5571
+ for (const pattern of allPatterns) {
5572
+ if (pattern.pattern.test(userAgent)) {
5573
+ matchedPattern = pattern;
5574
+ break;
5575
+ }
5576
+ }
5577
+ if (!matchedPattern && blockSuspiciousUA && isSuspiciousUA(userAgent)) {
5578
+ return {
5579
+ isBot: true,
5580
+ category: "unknown",
5581
+ confidence: 0.8,
5582
+ reason: "Suspicious User-Agent pattern",
5583
+ userAgent
5584
+ };
5585
+ }
5586
+ if (!matchedPattern) {
5587
+ return {
5588
+ isBot: false,
5589
+ confidence: 0.1,
5590
+ reason: "No bot pattern matched",
5591
+ userAgent
5592
+ };
5593
+ }
5594
+ if (blockList.includes(matchedPattern.name)) {
5595
+ return {
5596
+ isBot: true,
5597
+ category: matchedPattern.category,
5598
+ name: matchedPattern.name,
5599
+ confidence: 0.95,
5600
+ reason: `Blocked bot: ${matchedPattern.name}`,
5601
+ userAgent
5602
+ };
5603
+ }
5604
+ if (blockAllBots) {
5605
+ return {
5606
+ isBot: true,
5607
+ category: matchedPattern.category,
5608
+ name: matchedPattern.name,
5609
+ confidence: 0.9,
5610
+ reason: `Bot blocked (blockAllBots mode): ${matchedPattern.name}`,
5611
+ userAgent
5612
+ };
5613
+ }
5614
+ if (allowList.includes(matchedPattern.name)) {
5615
+ return {
5616
+ isBot: true,
5617
+ category: matchedPattern.category,
5618
+ name: matchedPattern.name,
5619
+ confidence: 0.95,
5620
+ reason: `Allowed bot: ${matchedPattern.name}`,
5621
+ userAgent
5622
+ };
5623
+ }
5624
+ if (allowCategories.includes(matchedPattern.category)) {
5625
+ return {
5626
+ isBot: true,
5627
+ category: matchedPattern.category,
5628
+ name: matchedPattern.name,
5629
+ confidence: 0.9,
5630
+ reason: `Allowed category: ${matchedPattern.category}`,
5631
+ userAgent
5632
+ };
5633
+ }
5634
+ return {
5635
+ isBot: true,
5636
+ category: matchedPattern.category,
5637
+ name: matchedPattern.name,
5638
+ confidence: 0.85,
5639
+ reason: matchedPattern.allowed ? `Allowed bot: ${matchedPattern.name}` : `Blocked bot: ${matchedPattern.name}`,
5640
+ userAgent
5641
+ };
5642
+ }
5643
+ function isSuspiciousUA(userAgent) {
5644
+ if (userAgent.length < 10) {
5645
+ return true;
5646
+ }
5647
+ if (/^[0-9a-f]{8,}$/i.test(userAgent)) {
5648
+ return true;
5649
+ }
5650
+ const hasBrowserIndicator = /Mozilla|Chrome|Safari|Firefox|Edge|Opera|MSIE|Trident/i.test(userAgent);
5651
+ const hasOSIndicator = /Windows|Mac|Linux|Android|iOS|iPhone|iPad/i.test(userAgent);
5652
+ if (hasBrowserIndicator && !hasOSIndicator && userAgent.length < 50) {
5653
+ return true;
5654
+ }
5655
+ if (/Chrome\/[0-4]\./i.test(userAgent) || /Firefox\/[0-3]\./i.test(userAgent)) {
5656
+ return true;
5657
+ }
5658
+ return false;
5659
+ }
5660
+ function isBotAllowed(botName, options = {}) {
5661
+ const {
5662
+ blockAllBots = false,
5663
+ allowCategories = DEFAULT_ALLOWED_CATEGORIES,
5664
+ allowList = DEFAULT_ALLOWED_BOTS,
5665
+ blockList = []
5666
+ } = options;
5667
+ if (blockList.includes(botName)) {
5668
+ return false;
5669
+ }
5670
+ if (blockAllBots) {
5671
+ return false;
5672
+ }
5673
+ if (allowList.includes(botName)) {
5674
+ return true;
5675
+ }
5676
+ const pattern = KNOWN_BOT_PATTERNS.find((p) => p.name === botName);
5677
+ if (pattern && allowCategories.includes(pattern.category)) {
5678
+ return true;
5679
+ }
5680
+ return pattern?.allowed ?? false;
5681
+ }
5682
+ function getBotsByCategory(category) {
5683
+ return KNOWN_BOT_PATTERNS.filter((p) => p.category === category);
5684
+ }
5685
+ function createBotPattern(name, pattern, category, allowed = false, description) {
5686
+ return {
5687
+ name,
5688
+ pattern: typeof pattern === "string" ? new RegExp(pattern, "i") : pattern,
5689
+ category,
5690
+ allowed,
5691
+ description
5692
+ };
5693
+ }
5694
+
5695
+ // src/middleware/bot/honeypot.ts
5696
+ var DEFAULT_HONEYPOT_FIELDS = [
5697
+ "_hp_email",
5698
+ "_hp_name",
5699
+ "_hp_website",
5700
+ "_hp_phone",
5701
+ "_hp_address",
5702
+ "email_confirm",
5703
+ "website_url",
5704
+ "fax_number"
5705
+ ];
5706
+ var DEFAULT_HONEYPOT_OPTIONS = {
5707
+ fieldName: "_hp_email",
5708
+ additionalFields: [],
5709
+ checkIn: ["body", "query"]};
5710
+ async function checkHoneypot(req, options = {}) {
5711
+ const {
5712
+ fieldName = DEFAULT_HONEYPOT_OPTIONS.fieldName,
5713
+ additionalFields = DEFAULT_HONEYPOT_OPTIONS.additionalFields,
5714
+ checkIn = DEFAULT_HONEYPOT_OPTIONS.checkIn,
5715
+ validate: validate2
5716
+ } = options;
5717
+ const allFields = [fieldName, ...additionalFields];
5718
+ const filledFields = [];
5719
+ if (checkIn.includes("query")) {
5720
+ const url = new URL(req.url);
5721
+ for (const field of allFields) {
5722
+ const value = url.searchParams.get(field);
5723
+ if (value !== null && value !== "") {
5724
+ filledFields.push(`query:${field}`);
5725
+ }
5726
+ }
5727
+ }
5728
+ if (checkIn.includes("body") && hasBody(req)) {
5729
+ try {
5730
+ const body = await getRequestBody(req);
5731
+ if (body && typeof body === "object") {
5732
+ for (const field of allFields) {
5733
+ const value = body[field];
5734
+ if (value !== void 0 && value !== null && value !== "") {
5735
+ if (validate2 && !validate2(value)) {
5736
+ continue;
5737
+ }
5738
+ filledFields.push(`body:${field}`);
5739
+ }
5740
+ }
5741
+ }
5742
+ } catch {
5743
+ }
5744
+ }
5745
+ if (checkIn.includes("headers")) {
5746
+ for (const field of allFields) {
5747
+ const headerName = `x-${field.replace(/_/g, "-")}`;
5748
+ const value = req.headers.get(headerName);
5749
+ if (value !== null && value !== "") {
5750
+ filledFields.push(`header:${headerName}`);
5751
+ }
5752
+ }
5753
+ }
5754
+ if (filledFields.length > 0) {
5755
+ return {
5756
+ isBot: true,
5757
+ category: "spam",
5758
+ confidence: 0.95,
5759
+ reason: `Honeypot triggered: ${filledFields.join(", ")}`,
5760
+ ip: getClientIP2(req)
5761
+ };
5762
+ }
5763
+ return {
5764
+ isBot: false,
5765
+ confidence: 0,
5766
+ reason: "Honeypot check passed"
5767
+ };
5768
+ }
5769
+ function withHoneypot(handler, options = {}) {
5770
+ return async (req, ctx) => {
5771
+ const result = await checkHoneypot(req, options);
5772
+ if (result.isBot) {
5773
+ return new Response(
5774
+ JSON.stringify({
5775
+ success: false,
5776
+ error: "Request rejected"
5777
+ }),
5778
+ {
5779
+ status: 403,
5780
+ headers: { "Content-Type": "application/json" }
5781
+ }
5782
+ );
5783
+ }
5784
+ return handler(req, ctx);
5785
+ };
5786
+ }
5787
+ function generateHoneypotHTML(options = {}) {
5788
+ const {
5789
+ fieldName = DEFAULT_HONEYPOT_OPTIONS.fieldName,
5790
+ additionalFields = []
5791
+ } = options;
5792
+ const allFields = [fieldName, ...additionalFields];
5793
+ const fields = allFields.map((field) => {
5794
+ const style = getRandomHidingStyle();
5795
+ const labelText = humanizeFieldName(field);
5796
+ return `
5797
+ <div style="${style}" aria-hidden="true" tabindex="-1">
5798
+ <label for="${field}">${labelText}</label>
5799
+ <input
5800
+ type="text"
5801
+ id="${field}"
5802
+ name="${field}"
5803
+ autocomplete="off"
5804
+ tabindex="-1"
5805
+ />
5806
+ </div>`;
5807
+ }).join("\n");
5808
+ return `<!-- Honeypot fields - Do not fill these -->
5809
+ ${fields}`;
5810
+ }
5811
+ function generateHoneypotCSS(options = {}) {
5812
+ const {
5813
+ fieldName = DEFAULT_HONEYPOT_OPTIONS.fieldName,
5814
+ additionalFields = []
5815
+ } = options;
5816
+ const allFields = [fieldName, ...additionalFields];
5817
+ const selectors = allFields.map((f) => `#${f}`).join(", ");
5818
+ return `
5819
+ /* Honeypot field hiding */
5820
+ ${selectors} {
5821
+ position: absolute !important;
5822
+ left: -9999px !important;
5823
+ top: -9999px !important;
5824
+ opacity: 0 !important;
5825
+ height: 0 !important;
5826
+ width: 0 !important;
5827
+ z-index: -1 !important;
5828
+ pointer-events: none !important;
5829
+ }
5830
+ `.trim();
5831
+ }
5832
+ function hasBody(req) {
5833
+ const method = req.method.toUpperCase();
5834
+ return ["POST", "PUT", "PATCH", "DELETE"].includes(method);
5835
+ }
5836
+ async function getRequestBody(req) {
5837
+ try {
5838
+ const contentType = req.headers.get("content-type") || "";
5839
+ if (contentType.includes("application/json")) {
5840
+ const cloned = req.clone();
5841
+ return await cloned.json();
5842
+ }
5843
+ if (contentType.includes("application/x-www-form-urlencoded")) {
5844
+ const cloned = req.clone();
5845
+ const text = await cloned.text();
5846
+ return Object.fromEntries(new URLSearchParams(text));
5847
+ }
5848
+ if (contentType.includes("multipart/form-data")) {
5849
+ const cloned = req.clone();
5850
+ const formData = await cloned.formData();
5851
+ const obj = {};
5852
+ formData.forEach((value, key) => {
5853
+ obj[key] = value;
5854
+ });
5855
+ return obj;
5856
+ }
5857
+ return null;
5858
+ } catch {
5859
+ return null;
5860
+ }
5861
+ }
5862
+ function getClientIP2(req) {
5863
+ return req.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || req.headers.get("x-real-ip") || req.headers.get("cf-connecting-ip") || "unknown";
5864
+ }
5865
+ function getRandomHidingStyle() {
5866
+ const styles = [
5867
+ "position: absolute; left: -9999px; top: -9999px;",
5868
+ "position: fixed; left: -100vw; visibility: hidden;",
5869
+ "opacity: 0; height: 0; width: 0; overflow: hidden;",
5870
+ "clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;",
5871
+ "transform: scale(0); position: absolute;"
5872
+ ];
5873
+ return styles[Math.floor(Math.random() * styles.length)];
5874
+ }
5875
+ function humanizeFieldName(field) {
5876
+ return field.replace(/^_hp_/, "").replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
5877
+ }
5878
+
5879
+ // src/middleware/bot/behavior.ts
5880
+ var DEFAULT_BEHAVIOR_OPTIONS = {
5881
+ minRequestInterval: 100,
5882
+ maxRequestsPerSecond: 10,
5883
+ windowMs: 6e4,
5884
+ patterns: {
5885
+ sequentialAccess: true,
5886
+ regularTiming: true,
5887
+ missingHeaders: true
5888
+ }
5889
+ };
5890
+ var MemoryBehaviorStore = class {
5891
+ records = /* @__PURE__ */ new Map();
5892
+ maxIdentifiers;
5893
+ accessOrder = [];
5894
+ constructor(options = {}) {
5895
+ this.maxIdentifiers = options.maxIdentifiers || 1e4;
5896
+ }
5897
+ async record(identifier, timestamp, path) {
5898
+ if (!this.records.has(identifier) && this.records.size >= this.maxIdentifiers) {
5899
+ const oldest = this.accessOrder.shift();
5900
+ if (oldest) {
5901
+ this.records.delete(oldest);
5902
+ }
5903
+ }
5904
+ const idx = this.accessOrder.indexOf(identifier);
5905
+ if (idx > -1) {
5906
+ this.accessOrder.splice(idx, 1);
5907
+ }
5908
+ this.accessOrder.push(identifier);
5909
+ const records = this.records.get(identifier) || [];
5910
+ records.push({ timestamp, path });
5911
+ this.records.set(identifier, records);
5912
+ }
5913
+ async getHistory(identifier, windowMs) {
5914
+ const records = this.records.get(identifier) || [];
5915
+ const cutoff = Date.now() - windowMs;
5916
+ const filtered = records.filter((r) => r.timestamp > cutoff);
5917
+ if (filtered.length !== records.length) {
5918
+ this.records.set(identifier, filtered);
5919
+ }
5920
+ return filtered;
5921
+ }
5922
+ async cleanup(maxAge) {
5923
+ const cutoff = Date.now() - maxAge;
5924
+ for (const [identifier, records] of this.records.entries()) {
5925
+ const filtered = records.filter((r) => r.timestamp > cutoff);
5926
+ if (filtered.length === 0) {
5927
+ this.records.delete(identifier);
5928
+ const idx = this.accessOrder.indexOf(identifier);
5929
+ if (idx > -1) {
5930
+ this.accessOrder.splice(idx, 1);
5931
+ }
5932
+ } else {
5933
+ this.records.set(identifier, filtered);
5934
+ }
5935
+ }
5936
+ }
5937
+ /**
5938
+ * Get store statistics
5939
+ */
5940
+ getStats() {
5941
+ let totalRecords = 0;
5942
+ for (const records of this.records.values()) {
5943
+ totalRecords += records.length;
5944
+ }
5945
+ return {
5946
+ identifiers: this.records.size,
5947
+ totalRecords
5948
+ };
5949
+ }
5950
+ /**
5951
+ * Clear all records
5952
+ */
5953
+ clear() {
5954
+ this.records.clear();
5955
+ this.accessOrder = [];
5956
+ }
5957
+ };
5958
+ async function analyzeBehavior(req, history, options = {}) {
5959
+ const {
5960
+ minRequestInterval = DEFAULT_BEHAVIOR_OPTIONS.minRequestInterval,
5961
+ maxRequestsPerSecond = DEFAULT_BEHAVIOR_OPTIONS.maxRequestsPerSecond,
5962
+ patterns = DEFAULT_BEHAVIOR_OPTIONS.patterns
5963
+ } = options;
5964
+ const reasons = [];
5965
+ let score = 0;
5966
+ const now = Date.now();
5967
+ const requestCount = history.length;
5968
+ const intervals = [];
5969
+ for (let i = 1; i < history.length; i++) {
5970
+ intervals.push(history[i].timestamp - history[i - 1].timestamp);
5971
+ }
5972
+ const avgInterval = intervals.length > 0 ? intervals.reduce((a, b) => a + b, 0) / intervals.length : Infinity;
5973
+ const oneSecondAgo = now - 1e3;
5974
+ const requestsLastSecond = history.filter((r) => r.timestamp > oneSecondAgo).length;
5975
+ if (requestsLastSecond > maxRequestsPerSecond) {
5976
+ score += 0.3;
5977
+ reasons.push(`High request rate: ${requestsLastSecond}/s (max: ${maxRequestsPerSecond})`);
5978
+ }
5979
+ const hasRapidRequests = intervals.some((i) => i < minRequestInterval);
5980
+ if (hasRapidRequests) {
5981
+ score += 0.25;
5982
+ reasons.push(`Rapid requests detected: interval < ${minRequestInterval}ms`);
5983
+ }
5984
+ if (patterns?.regularTiming && intervals.length >= 5) {
5985
+ const variance = calculateVariance(intervals);
5986
+ const coefficientOfVariation = Math.sqrt(variance) / avgInterval;
5987
+ if (coefficientOfVariation < 0.1) {
5988
+ score += 0.2;
5989
+ reasons.push("Suspiciously regular request timing");
5990
+ }
5991
+ }
5992
+ if (patterns?.sequentialAccess && history.length >= 5) {
5993
+ const paths = history.map((r) => r.path);
5994
+ if (isSequentialPattern(paths)) {
5995
+ score += 0.15;
5996
+ reasons.push("Sequential URL access pattern detected");
5997
+ }
5998
+ }
5999
+ if (patterns?.missingHeaders) {
6000
+ const missingScore = checkMissingHeaders(req);
6001
+ if (missingScore > 0) {
6002
+ score += missingScore;
6003
+ reasons.push("Missing typical browser headers");
6004
+ }
6005
+ }
6006
+ score = Math.min(1, score);
6007
+ return {
6008
+ suspicious: score >= 0.5,
6009
+ score,
6010
+ reasons,
6011
+ requestCount,
6012
+ avgInterval
6013
+ };
6014
+ }
6015
+ async function checkBehavior(req, options = {}) {
6016
+ const {
6017
+ store = new MemoryBehaviorStore(),
6018
+ windowMs = DEFAULT_BEHAVIOR_OPTIONS.windowMs,
6019
+ identifier: getIdentifier2
6020
+ } = options;
6021
+ const identifier = getIdentifier2 ? await getIdentifier2(req) : getClientIP3(req);
6022
+ const history = await store.getHistory(identifier, windowMs);
6023
+ const now = Date.now();
6024
+ const path = new URL(req.url).pathname;
6025
+ await store.record(identifier, now, path);
6026
+ const updatedHistory = [...history, { timestamp: now, path }];
6027
+ const analysis = await analyzeBehavior(req, updatedHistory, options);
6028
+ return {
6029
+ isBot: analysis.suspicious,
6030
+ confidence: analysis.score,
6031
+ reason: analysis.reasons.join("; ") || "Behavior analysis passed",
6032
+ ip: identifier
6033
+ };
6034
+ }
6035
+ var globalBehaviorStore;
6036
+ function getGlobalBehaviorStore() {
6037
+ if (!globalBehaviorStore) {
6038
+ globalBehaviorStore = new MemoryBehaviorStore();
6039
+ }
6040
+ return globalBehaviorStore;
6041
+ }
6042
+ function withBehaviorAnalysis(handler, options = {}) {
6043
+ const store = options.store || getGlobalBehaviorStore();
6044
+ const mergedOptions = { ...options, store };
6045
+ return async (req, ctx) => {
6046
+ const result = await checkBehavior(req, mergedOptions);
6047
+ if (result.isBot && result.confidence >= 0.5) {
6048
+ return new Response(
6049
+ JSON.stringify({
6050
+ error: "Too Many Requests",
6051
+ message: "Unusual request pattern detected",
6052
+ retryAfter: 60
6053
+ }),
6054
+ {
6055
+ status: 429,
6056
+ headers: {
6057
+ "Content-Type": "application/json",
6058
+ "Retry-After": "60"
6059
+ }
6060
+ }
6061
+ );
6062
+ }
6063
+ return handler(req, ctx);
6064
+ };
6065
+ }
6066
+ function getClientIP3(req) {
6067
+ return req.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || req.headers.get("x-real-ip") || req.headers.get("cf-connecting-ip") || "unknown";
6068
+ }
6069
+ function calculateVariance(numbers) {
6070
+ if (numbers.length === 0) return 0;
6071
+ const mean = numbers.reduce((a, b) => a + b, 0) / numbers.length;
6072
+ const squaredDiffs = numbers.map((n) => Math.pow(n - mean, 2));
6073
+ return squaredDiffs.reduce((a, b) => a + b, 0) / numbers.length;
6074
+ }
6075
+ function isSequentialPattern(paths) {
6076
+ const numbers = paths.map((p) => {
6077
+ const match = p.match(/(\d+)/);
6078
+ return match ? parseInt(match[1], 10) : null;
6079
+ }).filter((n) => n !== null);
6080
+ if (numbers.length < 3) return false;
6081
+ let sequential = 0;
6082
+ for (let i = 1; i < numbers.length; i++) {
6083
+ if (numbers[i] === numbers[i - 1] + 1) {
6084
+ sequential++;
6085
+ }
6086
+ }
6087
+ return sequential >= numbers.length * 0.6;
6088
+ }
6089
+ function checkMissingHeaders(req) {
6090
+ let score = 0;
6091
+ const typicalHeaders = [
6092
+ "accept",
6093
+ "accept-language",
6094
+ "accept-encoding"
6095
+ ];
6096
+ for (const header of typicalHeaders) {
6097
+ if (!req.headers.get(header)) {
6098
+ score += 0.05;
6099
+ }
6100
+ }
6101
+ const accept = req.headers.get("accept");
6102
+ if (accept && !accept.includes("text/html") && !accept.includes("application/json") && !accept.includes("*/*")) {
6103
+ score += 0.05;
6104
+ }
6105
+ const referer = req.headers.get("referer");
6106
+ const path = new URL(req.url).pathname;
6107
+ if (!referer && path !== "/" && !path.includes("/api/")) {
6108
+ score += 0.03;
6109
+ }
6110
+ return score;
6111
+ }
6112
+
6113
+ // src/middleware/bot/captcha.ts
6114
+ var CAPTCHA_VERIFY_URLS = {
6115
+ recaptcha: "https://www.google.com/recaptcha/api/siteverify",
6116
+ hcaptcha: "https://hcaptcha.com/siteverify",
6117
+ turnstile: "https://challenges.cloudflare.com/turnstile/v0/siteverify"
6118
+ };
6119
+ var DEFAULT_TOKEN_FIELDS = {
6120
+ recaptcha: "g-recaptcha-response",
6121
+ hcaptcha: "h-captcha-response",
6122
+ turnstile: "cf-turnstile-response"
6123
+ };
6124
+ async function verifyCaptcha(token, options) {
6125
+ const { provider, secretKey, action } = options;
6126
+ const verifyUrl = CAPTCHA_VERIFY_URLS[provider];
6127
+ const formData = new URLSearchParams();
6128
+ formData.append("secret", secretKey);
6129
+ formData.append("response", token);
6130
+ try {
6131
+ const response = await fetch(verifyUrl, {
6132
+ method: "POST",
6133
+ headers: {
6134
+ "Content-Type": "application/x-www-form-urlencoded"
6135
+ },
6136
+ body: formData.toString()
6137
+ });
6138
+ if (!response.ok) {
6139
+ return {
6140
+ success: false,
6141
+ errorCodes: [`HTTP ${response.status}`]
6142
+ };
6143
+ }
6144
+ const data = await response.json();
6145
+ return parseCaptchaResponse(data, provider, action);
6146
+ } catch (error) {
6147
+ return {
6148
+ success: false,
6149
+ errorCodes: ["verification-failed", String(error)]
6150
+ };
6151
+ }
6152
+ }
6153
+ function parseCaptchaResponse(data, provider, expectedAction) {
6154
+ switch (provider) {
6155
+ case "recaptcha":
6156
+ return parseRecaptchaResponse(data, expectedAction);
6157
+ case "hcaptcha":
6158
+ return parseHCaptchaResponse(data);
6159
+ case "turnstile":
6160
+ return parseTurnstileResponse(data);
6161
+ default:
6162
+ return {
6163
+ success: false,
6164
+ errorCodes: ["unknown-provider"]
6165
+ };
6166
+ }
6167
+ }
6168
+ function parseRecaptchaResponse(data, expectedAction) {
6169
+ const result = {
6170
+ success: data.success === true,
6171
+ score: typeof data.score === "number" ? data.score : void 0,
6172
+ action: typeof data.action === "string" ? data.action : void 0,
6173
+ hostname: typeof data.hostname === "string" ? data.hostname : void 0,
6174
+ challengeTs: typeof data.challenge_ts === "string" ? data.challenge_ts : void 0,
6175
+ errorCodes: Array.isArray(data["error-codes"]) ? data["error-codes"] : void 0
6176
+ };
6177
+ if (result.success && expectedAction && result.action !== expectedAction) {
6178
+ result.success = false;
6179
+ result.errorCodes = ["action-mismatch"];
6180
+ }
6181
+ return result;
6182
+ }
6183
+ function parseHCaptchaResponse(data) {
6184
+ return {
6185
+ success: data.success === true,
6186
+ hostname: typeof data.hostname === "string" ? data.hostname : void 0,
6187
+ challengeTs: typeof data.challenge_ts === "string" ? data.challenge_ts : void 0,
6188
+ errorCodes: Array.isArray(data["error-codes"]) ? data["error-codes"] : void 0
6189
+ };
6190
+ }
6191
+ function parseTurnstileResponse(data) {
6192
+ return {
6193
+ success: data.success === true,
6194
+ hostname: typeof data.hostname === "string" ? data.hostname : void 0,
6195
+ challengeTs: typeof data.challenge_ts === "string" ? data.challenge_ts : void 0,
6196
+ action: typeof data.action === "string" ? data.action : void 0,
6197
+ errorCodes: Array.isArray(data["error-codes"]) ? data["error-codes"] : void 0
6198
+ };
6199
+ }
6200
+ async function extractCaptchaToken(req, options) {
6201
+ const { provider, tokenField } = options;
6202
+ const fieldName = tokenField || DEFAULT_TOKEN_FIELDS[provider];
6203
+ const url = new URL(req.url);
6204
+ const queryToken = url.searchParams.get(fieldName);
6205
+ if (queryToken) {
6206
+ return queryToken;
6207
+ }
6208
+ if (hasBody2(req)) {
6209
+ try {
6210
+ const body = await getRequestBody2(req);
6211
+ if (body && typeof body === "object") {
6212
+ const bodyToken = body[fieldName];
6213
+ if (typeof bodyToken === "string") {
6214
+ return bodyToken;
6215
+ }
6216
+ }
6217
+ } catch {
6218
+ }
6219
+ }
6220
+ const headerToken = req.headers.get(`x-${fieldName}`);
6221
+ if (headerToken) {
6222
+ return headerToken;
6223
+ }
6224
+ return null;
6225
+ }
6226
+ async function checkCaptcha(req, options) {
6227
+ const { threshold = 0.5, skip } = options;
6228
+ if (skip && await skip(req)) {
6229
+ return {
6230
+ isBot: false,
6231
+ confidence: 0,
6232
+ reason: "CAPTCHA check skipped"
6233
+ };
6234
+ }
6235
+ const token = await extractCaptchaToken(req, options);
6236
+ if (!token) {
6237
+ return {
6238
+ isBot: true,
6239
+ confidence: 0.9,
6240
+ reason: "CAPTCHA token missing",
6241
+ ip: getClientIP4(req)
6242
+ };
6243
+ }
6244
+ const result = await verifyCaptcha(token, options);
6245
+ if (!result.success) {
6246
+ return {
6247
+ isBot: true,
6248
+ confidence: 0.95,
6249
+ reason: `CAPTCHA verification failed: ${result.errorCodes?.join(", ") || "unknown"}`,
6250
+ ip: getClientIP4(req)
6251
+ };
6252
+ }
6253
+ if (result.score !== void 0 && result.score < threshold) {
6254
+ return {
6255
+ isBot: true,
6256
+ confidence: 1 - result.score,
6257
+ reason: `CAPTCHA score too low: ${result.score} (threshold: ${threshold})`,
6258
+ ip: getClientIP4(req)
6259
+ };
6260
+ }
6261
+ return {
6262
+ isBot: false,
6263
+ confidence: result.score !== void 0 ? 1 - result.score : 0.1,
6264
+ reason: "CAPTCHA verification passed"
6265
+ };
6266
+ }
6267
+ function withCaptcha(handler, options) {
6268
+ return async (req, ctx) => {
6269
+ const result = await checkCaptcha(req, options);
6270
+ if (result.isBot) {
6271
+ return new Response(
6272
+ JSON.stringify({
6273
+ error: "CAPTCHA Required",
6274
+ message: result.reason,
6275
+ code: "CAPTCHA_FAILED"
6276
+ }),
6277
+ {
6278
+ status: 403,
6279
+ headers: { "Content-Type": "application/json" }
6280
+ }
6281
+ );
6282
+ }
6283
+ return handler(req, ctx);
6284
+ };
6285
+ }
6286
+ function generateRecaptchaV2(siteKey, options = {}) {
6287
+ const { theme = "light", size = "normal" } = options;
6288
+ return `
6289
+ <script src="https://www.google.com/recaptcha/api.js" async defer></script>
6290
+ <div class="g-recaptcha" data-sitekey="${siteKey}" data-theme="${theme}" data-size="${size}"></div>
6291
+ `.trim();
6292
+ }
6293
+ function generateRecaptchaV3(siteKey, action = "submit") {
6294
+ return `
6295
+ <script src="https://www.google.com/recaptcha/api.js?render=${siteKey}"></script>
6296
+ <script>
6297
+ function getRecaptchaToken() {
6298
+ return new Promise((resolve, reject) => {
6299
+ grecaptcha.ready(() => {
6300
+ grecaptcha.execute('${siteKey}', { action: '${action}' })
6301
+ .then(resolve)
6302
+ .catch(reject);
6303
+ });
6304
+ });
6305
+ }
6306
+ </script>
6307
+ `.trim();
6308
+ }
6309
+ function generateHCaptcha(siteKey, options = {}) {
6310
+ const { theme = "light", size = "normal" } = options;
6311
+ return `
6312
+ <script src="https://js.hcaptcha.com/1/api.js" async defer></script>
6313
+ <div class="h-captcha" data-sitekey="${siteKey}" data-theme="${theme}" data-size="${size}"></div>
6314
+ `.trim();
6315
+ }
6316
+ function generateTurnstile(siteKey, options = {}) {
6317
+ const { theme = "auto", size = "normal" } = options;
6318
+ return `
6319
+ <script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
6320
+ <div class="cf-turnstile" data-sitekey="${siteKey}" data-theme="${theme}" data-size="${size}"></div>
6321
+ `.trim();
6322
+ }
6323
+ function hasBody2(req) {
6324
+ const method = req.method.toUpperCase();
6325
+ return ["POST", "PUT", "PATCH", "DELETE"].includes(method);
6326
+ }
6327
+ async function getRequestBody2(req) {
6328
+ try {
6329
+ const contentType = req.headers.get("content-type") || "";
6330
+ if (contentType.includes("application/json")) {
6331
+ const cloned = req.clone();
6332
+ return await cloned.json();
6333
+ }
6334
+ if (contentType.includes("application/x-www-form-urlencoded")) {
6335
+ const cloned = req.clone();
6336
+ const text = await cloned.text();
6337
+ return Object.fromEntries(new URLSearchParams(text));
6338
+ }
6339
+ if (contentType.includes("multipart/form-data")) {
6340
+ const cloned = req.clone();
6341
+ const formData = await cloned.formData();
6342
+ const obj = {};
6343
+ formData.forEach((value, key) => {
6344
+ obj[key] = value;
6345
+ });
6346
+ return obj;
6347
+ }
6348
+ return null;
6349
+ } catch {
6350
+ return null;
6351
+ }
6352
+ }
6353
+ function getClientIP4(req) {
6354
+ return req.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || req.headers.get("x-real-ip") || req.headers.get("cf-connecting-ip") || "unknown";
6355
+ }
6356
+
6357
+ // src/middleware/bot/middleware.ts
6358
+ function defaultBotResponse(result) {
6359
+ return new Response(
6360
+ JSON.stringify({
6361
+ error: "Forbidden",
6362
+ message: result.reason || "Request blocked",
6363
+ code: "BOT_DETECTED",
6364
+ category: result.category
6365
+ }),
6366
+ {
6367
+ status: 403,
6368
+ headers: {
6369
+ "Content-Type": "application/json",
6370
+ "X-Bot-Detection": "true"
6371
+ }
6372
+ }
6373
+ );
6374
+ }
6375
+ async function detectBot(req, options = {}) {
6376
+ const results = [];
6377
+ if (options.userAgent !== false) {
6378
+ const uaOptions = options.userAgent === true ? {} : options.userAgent || {};
6379
+ const userAgent = req.headers.get("user-agent");
6380
+ const uaResult = analyzeUserAgent(userAgent, uaOptions);
6381
+ if (uaResult.isBot && !isAllowedBot(uaResult, uaOptions)) {
6382
+ results.push(uaResult);
6383
+ }
6384
+ }
6385
+ if (options.honeypot !== false && hasBody3(req)) {
6386
+ const hpOptions = options.honeypot === true ? {} : options.honeypot || {};
6387
+ const hpResult = await checkHoneypot(req, hpOptions);
6388
+ if (hpResult.isBot) {
6389
+ results.push(hpResult);
6390
+ }
6391
+ }
6392
+ if (options.behavior !== false) {
6393
+ const behaviorOptions = options.behavior === true ? {} : options.behavior || {};
6394
+ if (!behaviorOptions.store) {
6395
+ behaviorOptions.store = getGlobalBehaviorStore();
6396
+ }
6397
+ const behaviorResult = await checkBehavior(req, behaviorOptions);
6398
+ if (behaviorResult.isBot) {
6399
+ results.push(behaviorResult);
6400
+ }
6401
+ }
6402
+ if (options.captcha) {
6403
+ const captchaResult = await checkCaptcha(req, options.captcha);
6404
+ if (captchaResult.isBot) {
6405
+ results.push(captchaResult);
6406
+ }
6407
+ }
6408
+ if (results.length === 0) {
6409
+ return {
6410
+ isBot: false,
6411
+ confidence: 0,
6412
+ reason: "All checks passed",
6413
+ ip: getClientIP5(req),
6414
+ userAgent: req.headers.get("user-agent") || void 0
6415
+ };
6416
+ }
6417
+ const highestConfidence = results.reduce(
6418
+ (prev, curr) => curr.confidence > prev.confidence ? curr : prev
6419
+ );
6420
+ const allReasons = results.map((r) => r.reason).filter(Boolean).join("; ");
6421
+ return {
6422
+ isBot: true,
6423
+ category: highestConfidence.category,
6424
+ name: highestConfidence.name,
6425
+ confidence: highestConfidence.confidence,
6426
+ reason: allReasons,
6427
+ ip: getClientIP5(req),
6428
+ userAgent: req.headers.get("user-agent") || void 0
6429
+ };
6430
+ }
6431
+ function isAllowedBot(result, options) {
6432
+ const {
6433
+ allowCategories = DEFAULT_ALLOWED_CATEGORIES,
6434
+ allowList = DEFAULT_ALLOWED_BOTS,
6435
+ blockList = [],
6436
+ blockAllBots = false
6437
+ } = options;
6438
+ if (result.name && blockList.includes(result.name)) {
6439
+ return false;
6440
+ }
6441
+ if (blockAllBots) {
6442
+ return false;
6443
+ }
6444
+ if (result.name && allowList.includes(result.name)) {
6445
+ return true;
6446
+ }
6447
+ if (result.category && allowCategories.includes(result.category)) {
6448
+ return true;
6449
+ }
6450
+ return false;
6451
+ }
6452
+ function withBotProtection(handler, options = {}) {
6453
+ const {
6454
+ skip,
6455
+ onBot,
6456
+ log,
6457
+ mode = "block"
6458
+ } = options;
6459
+ return async (req, ctx) => {
6460
+ if (skip && await skip(req)) {
6461
+ return handler(req, { ...ctx, bot: void 0 });
6462
+ }
6463
+ const result = await detectBot(req, options);
6464
+ if (log) {
6465
+ if (typeof log === "function") {
6466
+ log(result);
6467
+ } else if (result.isBot) {
6468
+ console.log("[Bot Detection]", JSON.stringify(result));
6469
+ }
6470
+ }
6471
+ if (result.isBot) {
6472
+ if (mode === "block") {
6473
+ if (onBot) {
6474
+ return onBot(req, result);
6475
+ }
6476
+ return defaultBotResponse(result);
6477
+ }
6478
+ }
6479
+ const extendedCtx = {
6480
+ ...ctx,
6481
+ bot: result
6482
+ };
6483
+ return handler(req, extendedCtx);
6484
+ };
6485
+ }
6486
+ function withUserAgentProtection(handler, options = {}) {
6487
+ return withBotProtection(handler, {
6488
+ userAgent: options,
6489
+ honeypot: false,
6490
+ behavior: false
6491
+ });
6492
+ }
6493
+ function withHoneypotProtection(handler, options = {}) {
6494
+ return withBotProtection(handler, {
6495
+ userAgent: false,
6496
+ honeypot: options,
6497
+ behavior: false
6498
+ });
6499
+ }
6500
+ function withBehaviorProtection(handler, options = {}) {
6501
+ return withBotProtection(handler, {
6502
+ userAgent: false,
6503
+ honeypot: false,
6504
+ behavior: options
6505
+ });
6506
+ }
6507
+ function withCaptchaProtection(handler, options) {
6508
+ return withBotProtection(handler, {
6509
+ userAgent: false,
6510
+ honeypot: false,
6511
+ behavior: false,
6512
+ captcha: options
6513
+ });
6514
+ }
6515
+ var BOT_PROTECTION_PRESETS = {
6516
+ /**
6517
+ * Relaxed - Only blocks obvious bots
6518
+ */
6519
+ relaxed: {
6520
+ userAgent: {
6521
+ blockAllBots: false,
6522
+ allowCategories: ["search_engine", "social_media", "monitoring", "feed_reader", "preview", "seo"]
6523
+ },
6524
+ honeypot: false,
6525
+ behavior: false
6526
+ },
6527
+ /**
6528
+ * Standard - Good balance of protection
6529
+ */
6530
+ standard: {
6531
+ userAgent: {
6532
+ blockAllBots: false,
6533
+ allowCategories: ["search_engine", "social_media", "monitoring"]
6534
+ },
6535
+ honeypot: true,
6536
+ behavior: {
6537
+ maxRequestsPerSecond: 10
6538
+ }
6539
+ },
6540
+ /**
6541
+ * Strict - Maximum protection
6542
+ */
6543
+ strict: {
6544
+ userAgent: {
6545
+ blockAllBots: false,
6546
+ allowCategories: ["search_engine"],
6547
+ blockEmptyUA: true,
6548
+ blockSuspiciousUA: true
6549
+ },
6550
+ honeypot: {
6551
+ additionalFields: ["_hp_name", "_hp_phone"]
6552
+ },
6553
+ behavior: {
6554
+ maxRequestsPerSecond: 5,
6555
+ minRequestInterval: 200
6556
+ }
6557
+ },
6558
+ /**
6559
+ * API - For API endpoints
6560
+ */
6561
+ api: {
6562
+ userAgent: {
6563
+ blockAllBots: true,
6564
+ blockEmptyUA: true
6565
+ },
6566
+ honeypot: false,
6567
+ behavior: {
6568
+ maxRequestsPerSecond: 20
6569
+ }
6570
+ }
6571
+ };
6572
+ function withBotProtectionPreset(handler, preset, overrides = {}) {
6573
+ const presetOptions = BOT_PROTECTION_PRESETS[preset];
6574
+ const mergedOptions = { ...presetOptions, ...overrides };
6575
+ return withBotProtection(handler, mergedOptions);
6576
+ }
6577
+ function hasBody3(req) {
6578
+ const method = req.method.toUpperCase();
6579
+ return ["POST", "PUT", "PATCH", "DELETE"].includes(method);
6580
+ }
6581
+ function getClientIP5(req) {
6582
+ return req.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || req.headers.get("x-real-ip") || req.headers.get("cf-connecting-ip") || "unknown";
6583
+ }
6584
+
5108
6585
  // src/index.ts
5109
- var VERSION = "0.6.0";
6586
+ var VERSION = "0.7.0";
5110
6587
 
5111
- export { MemoryStore2 as AuditMemoryStore, AuthenticationError, AuthorizationError, CLFFormatter, ConfigurationError, ConsoleStore, CsrfError, DANGEROUS_EXTENSIONS, DEFAULT_PII_FIELDS, ExternalStore, JSONFormatter, MIME_TYPES, MemoryStore, MultiStore, PRESET_API, PRESET_RELAXED, PRESET_STRICT, RateLimitError, SecureError, SecurityEventTracker, StructuredFormatter, TextFormatter, VERSION, ValidationError, anonymizeIp, buildCSP, buildHSTS, buildPermissionsPolicy, checkRateLimit, clearAllRateLimits, createMemoryStore2 as createAuditMemoryStore, createAuditMiddleware, createCLFFormatter, createToken as createCSRFToken, createConsoleStore, createDatadogStore, createExternalStore, createJSONFormatter, createMemoryStore, createMultiStore, createRateLimiter, createRedactor, createSecurityHeaders, createSecurityHeadersObject, createSecurityTracker, createStructuredFormatter, createTextFormatter, createValidator, decodeJWT, detectFileType, detectSQLInjection, detectXSS, escapeHtml, extractBearerToken, formatDuration, generateCSRF, getClientIp, getGeoInfo, getGlobalMemoryStore, getPreset, getRateLimitStatus, hasPathTraversal, hasSQLInjection, isFormRequest, isJsonRequest, isLocalhost, isPrivateIp, isSecureError, isValidIp, mask, normalizeIp, nowInMs, nowInSeconds, parseDuration, redactCreditCard, redactEmail, redactHeaders, redactIP, redactObject, redactPhone, redactQuery, resetRateLimit, sanitize, sanitizeFields, sanitizeFilename, sanitizeObject, sanitizePath, sanitizeSQLInput, sleep, stripHtml, toSecureError, tokensMatch, trackSecurityEvent, validate, validateBody, validateCSRF, validateContentType, validateFile, validateFiles, validatePath, validateQuery, validateRequest, verifyToken as verifyCSRFToken, verifyJWT, withAPIKey, withAuditLog, withAuth, withCSRF, withContentType, withFileValidation, withJWT, withOptionalAuth, withRateLimit, withRequestId, withRoles, withSQLProtection, withSanitization, withSecureValidation, withSecurityHeaders, withSession, withTiming, withValidation, withXSSProtection };
6588
+ export { MemoryStore2 as AuditMemoryStore, AuthenticationError, AuthorizationError, BOT_PROTECTION_PRESETS, CLFFormatter, ConfigurationError, ConsoleStore, CsrfError, DANGEROUS_EXTENSIONS, DEFAULT_ALLOWED_BOTS, DEFAULT_ALLOWED_CATEGORIES, DEFAULT_HONEYPOT_FIELDS, DEFAULT_PII_FIELDS, ExternalStore, JSONFormatter, KNOWN_BOT_PATTERNS, MIME_TYPES, MemoryBehaviorStore, MemoryStore, MultiStore, PRESET_API, PRESET_RELAXED, PRESET_STRICT, RateLimitError, SecureError, SecurityEventTracker, StructuredFormatter, TextFormatter, VERSION, ValidationError, analyzeBehavior, analyzeUserAgent, anonymizeIp, buildCSP, buildHSTS, buildPermissionsPolicy, checkBehavior, checkCaptcha, checkHoneypot, checkRateLimit, clearAllRateLimits, createMemoryStore2 as createAuditMemoryStore, createAuditMiddleware, createBotPattern, createCLFFormatter, createToken as createCSRFToken, createConsoleStore, createDatadogStore, createExternalStore, createJSONFormatter, createMemoryStore, createMultiStore, createRateLimiter, createRedactor, createSecurityHeaders, createSecurityHeadersObject, createSecurityTracker, createStructuredFormatter, createTextFormatter, createValidator, decodeJWT, detectBot, detectFileType, detectSQLInjection, detectXSS, escapeHtml, extractBearerToken, formatDuration, generateCSRF, generateHCaptcha, generateHoneypotCSS, generateHoneypotHTML, generateRecaptchaV2, generateRecaptchaV3, generateTurnstile, getBotsByCategory, getClientIp, getGeoInfo, getGlobalBehaviorStore, getGlobalMemoryStore, getPreset, getRateLimitStatus, hasPathTraversal, hasSQLInjection, isBotAllowed, isFormRequest, isJsonRequest, isLocalhost, isPrivateIp, isSecureError, isSuspiciousUA, isValidIp, mask, normalizeIp, nowInMs, nowInSeconds, parseDuration, redactCreditCard, redactEmail, redactHeaders, redactIP, redactObject, redactPhone, redactQuery, resetRateLimit, sanitize, sanitizeFields, sanitizeFilename, sanitizeObject, sanitizePath, sanitizeSQLInput, sleep, stripHtml, toSecureError, tokensMatch, trackSecurityEvent, validate, validateBody, validateCSRF, validateContentType, validateFile, validateFiles, validatePath, validateQuery, validateRequest, verifyToken as verifyCSRFToken, verifyCaptcha, verifyJWT, withAPIKey, withAuditLog, withAuth, withBehaviorAnalysis, withBehaviorProtection, withBotProtection, withBotProtectionPreset, withCSRF, withCaptcha, withCaptchaProtection, withContentType, withFileValidation, withHoneypot, withHoneypotProtection, withJWT, withOptionalAuth, withRateLimit, withRequestId, withRoles, withSQLProtection, withSanitization, withSecureValidation, withSecurityHeaders, withSession, withTiming, withUserAgentProtection, withValidation, withXSSProtection };
5112
6589
  //# sourceMappingURL=index.js.map
5113
6590
  //# sourceMappingURL=index.js.map