traffic-guard 0.1.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.cjs ADDED
@@ -0,0 +1,1119 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ BotGate: () => BotGate,
34
+ DEFAULT_POLICIES: () => DEFAULT_POLICIES,
35
+ TrafficGuard: () => TrafficGuard,
36
+ botgate: () => botgate,
37
+ calculateShannonEntropy: () => calculateShannonEntropy,
38
+ checkHeaderOrderAnomaly: () => checkHeaderOrderAnomaly,
39
+ createBotGateBattery: () => createBotGateBattery,
40
+ createClientHash: () => createClientHash,
41
+ createPoWChallenge: () => createPoWChallenge,
42
+ default: () => index_default,
43
+ evaluateDecision: () => evaluateDecision,
44
+ generateChallengeHtml: () => generateChallengeHtml,
45
+ heuristicAssessment: () => heuristicAssessment,
46
+ normalizeRequest: () => normalizeRequest,
47
+ resolvePolicy: () => resolvePolicy,
48
+ signBotToken: () => signBotToken,
49
+ trafficGuard: () => trafficGuard,
50
+ trafficguard: () => trafficguard,
51
+ verifyBotToken: () => verifyBotToken,
52
+ verifyPoW: () => verifyPoW
53
+ });
54
+ module.exports = __toCommonJS(index_exports);
55
+
56
+ // src/battery.ts
57
+ var sdkChoice = null;
58
+ var sdkNoul = null;
59
+ var sdkScore = null;
60
+ var sdkLoaded = false;
61
+ async function loadTypeSafePrimitives() {
62
+ if (!sdkLoaded) {
63
+ try {
64
+ const sdk = await import("@typesafe-ai/sdk");
65
+ sdkChoice = sdk.choice;
66
+ sdkNoul = sdk.noul;
67
+ sdkScore = sdk.score;
68
+ sdkLoaded = true;
69
+ } catch {
70
+ sdkLoaded = true;
71
+ }
72
+ }
73
+ return { choice: sdkChoice, noul: sdkNoul, score: sdkScore };
74
+ }
75
+ function createBotGateBattery() {
76
+ if (sdkNoul && sdkChoice && sdkScore) {
77
+ return {
78
+ is_bot: sdkNoul(
79
+ "Evaluate whether the incoming HTTP request in `request` is generated by an automated bot, script, crawler, scraper, or headless browser, rather than a genuine human user interacting with a standard web browser.",
80
+ {
81
+ true: "Clear indicators of automation, scripting libraries, automated headless execution, scraper activity, or non-human interaction patterns.",
82
+ false: "Natural human interactive web browser navigation."
83
+ }
84
+ ),
85
+ is_attack: sdkNoul(
86
+ "Evaluate whether the request contains an attack payload, exploit attempt, injection, path traversal, probe for sensitive files, or vulnerability scan.",
87
+ {
88
+ true: "Malicious payloads, SQLi, XSS, SSRF, command injection, path traversal, exploit probes, or sensitive credential/file hunting.",
89
+ false: "Safe, legitimate, benign application interaction without exploitation intent."
90
+ }
91
+ ),
92
+ is_spoofed: sdkNoul(
93
+ "Evaluate whether the request headers exhibit spoofing or client impersonation (such as claiming a browser User-Agent while lacking standard browser headers, or conflicting platform tokens).",
94
+ {
95
+ true: "Mismatched, counterfeit, or contradictory headers indicating spoofed client identity.",
96
+ false: "Headers are coherent and authentic for the specified client."
97
+ }
98
+ ),
99
+ traffic_type: sdkChoice(
100
+ "Classify the primary nature of this incoming HTTP traffic.",
101
+ {
102
+ human: "Legitimate human visitor navigating the website.",
103
+ good_bot: "Beneficial search engine indexer, feed fetcher, or uptime monitor (e.g. Googlebot, Bingbot, Slackbot).",
104
+ bad_bot: "Automated scraper, content scraper, spam bot, or unauthorized scraping tool.",
105
+ attack: "Hostile attacker executing vulnerability scans, exploit probes, or injection attacks."
106
+ }
107
+ ),
108
+ risk_level: sdkScore(
109
+ "Rate the security and operational risk this request poses to the website or service.",
110
+ [
111
+ "Level 0: Safe - normal human traffic or well-behaved verified search crawler.",
112
+ "Level 1: Low risk - minor header anomaly or benign unfamiliar crawler without malicious activity.",
113
+ "Level 2: Suspicious - aggressive automated scraping, suspicious probes, or inconsistent headers.",
114
+ "Level 3: Dangerous - active exploitation, injection payload, unauthorized sensitive path probe, or credential abuse."
115
+ ]
116
+ )
117
+ };
118
+ }
119
+ return {
120
+ is_bot: { type: "noul", instructions: "Is bot?" },
121
+ is_attack: { type: "noul", instructions: "Is attack?" },
122
+ is_spoofed: { type: "noul", instructions: "Is spoofed?" },
123
+ traffic_type: { type: "choice", instructions: "Traffic type" },
124
+ risk_level: { type: "score", instructions: "Risk level" }
125
+ };
126
+ }
127
+ function calculateShannonEntropy(str) {
128
+ if (!str || str.length === 0) return 0;
129
+ const len = str.length;
130
+ const frequencies = new Uint16Array(256);
131
+ for (let i = 0; i < len; i++) {
132
+ frequencies[str.charCodeAt(i) & 255]++;
133
+ }
134
+ let entropy = 0;
135
+ for (let i = 0; i < 256; i++) {
136
+ const count = frequencies[i];
137
+ if (count > 0) {
138
+ const p = count / len;
139
+ entropy -= p * Math.log2(p);
140
+ }
141
+ }
142
+ return entropy;
143
+ }
144
+ function heuristicAssessment(request, context) {
145
+ const userAgent = (request.headers["user-agent"] || "").toLowerCase();
146
+ const path = request.path.toLowerCase();
147
+ const queryStr = JSON.stringify(request.query).toLowerCase();
148
+ const bodyStr = typeof request.body === "string" ? request.body.toLowerCase() : JSON.stringify(request.body || "").toLowerCase();
149
+ const headersStr = JSON.stringify(request.headers).toLowerCase();
150
+ const fullTarget = `${path} ${queryStr} ${bodyStr} ${headersStr}`;
151
+ let botScore = 0.05;
152
+ let attackScore = 0.02;
153
+ let spoofScore = 0.05;
154
+ if (context.isKnownSearchBot) {
155
+ botScore = 0.95;
156
+ attackScore = 0.01;
157
+ return {
158
+ isBotProbability: botScore,
159
+ isAttackProbability: attackScore,
160
+ isSpoofedProbability: 0.02,
161
+ trafficType: "good_bot",
162
+ trafficTypeConfidence: 0.96,
163
+ trafficTypeDistribution: { good_bot: 0.94, human: 0.02, bad_bot: 0.03, attack: 0.01 },
164
+ riskScore: 0.1,
165
+ riskConfidence: 0.95,
166
+ riskLevelDistribution: { "0": 0.92, "1": 0.07, "2": 0.01, "3": 0 }
167
+ };
168
+ }
169
+ if (context.suspiciousSignatures.length > 0) {
170
+ botScore = 0.99;
171
+ attackScore = 0.98;
172
+ spoofScore = 0.85;
173
+ }
174
+ const cliTools = [
175
+ "curl/",
176
+ "python-requests",
177
+ "aiohttp",
178
+ "httpx",
179
+ "wget/",
180
+ "go-http-client",
181
+ "axios/",
182
+ "node-fetch",
183
+ "libwww-perl",
184
+ "postmanruntime",
185
+ "scrapy"
186
+ ];
187
+ if (cliTools.some((t) => userAgent.includes(t))) {
188
+ botScore = Math.max(botScore, 0.92);
189
+ }
190
+ if (userAgent.includes("headlesschrome") || userAgent.includes("phantomjs") || userAgent.includes("selenium") || userAgent.includes("puppeteer")) {
191
+ botScore = Math.max(botScore, 0.97);
192
+ spoofScore = Math.max(spoofScore, 0.8);
193
+ }
194
+ if (context.claimsBrowser && context.missingBrowserHeaders.length > 0) {
195
+ spoofScore = Math.max(spoofScore, 0.4 + context.missingBrowserHeaders.length * 0.2);
196
+ botScore = Math.max(botScore, 0.65);
197
+ }
198
+ const sensitivePaths = [
199
+ "/.env",
200
+ "/wp-login.php",
201
+ "/wp-admin",
202
+ "/.git",
203
+ "/actuator",
204
+ "/phpmyadmin",
205
+ "/config.json",
206
+ "/database.yml",
207
+ "/.aws/credentials"
208
+ ];
209
+ if (sensitivePaths.some((p) => path.includes(p))) {
210
+ attackScore = Math.max(attackScore, 0.85);
211
+ botScore = Math.max(botScore, 0.8);
212
+ }
213
+ const sqliSignatures = [
214
+ "' or 1=1",
215
+ '" or 1=1',
216
+ "union select",
217
+ "union all select",
218
+ "sleep(",
219
+ "benchmark(",
220
+ "information_schema",
221
+ "select sleep"
222
+ ];
223
+ const xssSignatures = ["<script>", "javascript:", "<svg onload", "onerror=alert"];
224
+ const traversalSignatures = ["../", "..\\", "%2e%2e%2f", "%252e%252e"];
225
+ const cmdSignatures = ["; cat ", "| whoami", "&& id", "`whoami`", "${jndi:"];
226
+ if (sqliSignatures.some((sig) => fullTarget.includes(sig))) {
227
+ attackScore = Math.max(attackScore, 0.96);
228
+ botScore = Math.max(botScore, 0.75);
229
+ }
230
+ if (xssSignatures.some((sig) => fullTarget.includes(sig))) {
231
+ attackScore = Math.max(attackScore, 0.92);
232
+ }
233
+ if (traversalSignatures.some((sig) => fullTarget.includes(sig))) {
234
+ attackScore = Math.max(attackScore, 0.95);
235
+ botScore = Math.max(botScore, 0.8);
236
+ }
237
+ if (cmdSignatures.some((sig) => fullTarget.includes(sig))) {
238
+ attackScore = Math.max(attackScore, 0.98);
239
+ }
240
+ if (queryStr.length > 30) {
241
+ const entropy = calculateShannonEntropy(queryStr);
242
+ if (entropy > 4.6 && attackScore < 0.6) {
243
+ attackScore = Math.max(attackScore, 0.65);
244
+ }
245
+ }
246
+ let trafficType = "human";
247
+ if (attackScore >= 0.7) {
248
+ trafficType = "attack";
249
+ } else if (botScore >= 0.6) {
250
+ trafficType = "bad_bot";
251
+ }
252
+ let calculatedRisk = attackScore * 2.8 + botScore * 0.7;
253
+ if (calculatedRisk > 3) calculatedRisk = 3;
254
+ const typeDistribution = {
255
+ human: trafficType === "human" ? 0.85 : 0.05,
256
+ good_bot: 0.02,
257
+ bad_bot: trafficType === "bad_bot" ? 0.8 : 0.1,
258
+ attack: trafficType === "attack" ? 0.9 : 0.05
259
+ };
260
+ const total = Object.values(typeDistribution).reduce((a, b) => a + b, 0);
261
+ for (const k of Object.keys(typeDistribution)) {
262
+ typeDistribution[k] = Math.round(typeDistribution[k] / total * 100) / 100;
263
+ }
264
+ return {
265
+ isBotProbability: Math.min(1, Math.round(botScore * 100) / 100),
266
+ isAttackProbability: Math.min(1, Math.round(attackScore * 100) / 100),
267
+ isSpoofedProbability: Math.min(1, Math.round(spoofScore * 100) / 100),
268
+ trafficType,
269
+ trafficTypeConfidence: 0.88,
270
+ trafficTypeDistribution: typeDistribution,
271
+ riskScore: Math.round(calculatedRisk * 100) / 100,
272
+ riskConfidence: 0.85,
273
+ riskLevelDistribution: {
274
+ "0": calculatedRisk < 0.5 ? 0.9 : 0.05,
275
+ "1": calculatedRisk >= 0.5 && calculatedRisk < 1.5 ? 0.8 : 0.1,
276
+ "2": calculatedRisk >= 1.5 && calculatedRisk < 2.5 ? 0.85 : 0.1,
277
+ "3": calculatedRisk >= 2.5 ? 0.9 : 0.05
278
+ }
279
+ };
280
+ }
281
+
282
+ // src/crypto.ts
283
+ var import_node_crypto = require("node:crypto");
284
+ var DEFAULT_SECRET = "traffic-guard-default-secret-key-32b!";
285
+ function createClientHash(ip = "", userAgent = "") {
286
+ return (0, import_node_crypto.createHash)("sha256").update(`${ip}::${userAgent.toLowerCase()}`).digest("hex").slice(0, 16);
287
+ }
288
+ function signBotToken(payload, secret = DEFAULT_SECRET) {
289
+ const jsonStr = JSON.stringify(payload);
290
+ const b64Payload = Buffer.from(jsonStr, "utf8").toString("base64url");
291
+ const signature = (0, import_node_crypto.createHmac)("sha256", secret).update(b64Payload).digest("base64url");
292
+ return `${b64Payload}.${signature}`;
293
+ }
294
+ function verifyBotToken(token, secret = DEFAULT_SECRET) {
295
+ try {
296
+ const parts = token.split(".");
297
+ if (parts.length !== 2) return null;
298
+ const [b64Payload, signature] = parts;
299
+ const expectedSig = (0, import_node_crypto.createHmac)("sha256", secret).update(b64Payload).digest("base64url");
300
+ if (signature !== expectedSig) return null;
301
+ const jsonStr = Buffer.from(b64Payload, "base64url").toString("utf8");
302
+ return JSON.parse(jsonStr);
303
+ } catch {
304
+ return null;
305
+ }
306
+ }
307
+ function parseCookies(cookieHeader = "") {
308
+ const cookies = {};
309
+ if (!cookieHeader) return cookies;
310
+ const pairs = cookieHeader.split(";");
311
+ for (const pair of pairs) {
312
+ const idx = pair.indexOf("=");
313
+ if (idx > 0) {
314
+ const key = pair.slice(0, idx).trim();
315
+ const val = pair.slice(idx + 1).trim();
316
+ cookies[key] = val;
317
+ }
318
+ }
319
+ return cookies;
320
+ }
321
+ function createPoWChallenge(secret = DEFAULT_SECRET, difficulty = 4) {
322
+ const ts = Date.now();
323
+ const rand = (0, import_node_crypto.randomBytes)(8).toString("hex");
324
+ const payload = `${ts}:${rand}:${difficulty}`;
325
+ const sig = (0, import_node_crypto.createHmac)("sha256", secret).update(payload).digest("hex").slice(0, 16);
326
+ const seed = `${payload}:${sig}`;
327
+ return { seed, difficulty };
328
+ }
329
+ function verifyPoW(seed, nonce, minDifficulty = 1, secret = DEFAULT_SECRET) {
330
+ try {
331
+ const parts = seed.split(":");
332
+ if (parts.length !== 4) return false;
333
+ const [tsStr, rand, diffStr, sig] = parts;
334
+ const payload = `${tsStr}:${rand}:${diffStr}`;
335
+ const expectedSig = (0, import_node_crypto.createHmac)("sha256", secret).update(payload).digest("hex").slice(0, 16);
336
+ if (sig !== expectedSig) return false;
337
+ const ts = parseInt(tsStr, 10);
338
+ if (Date.now() - ts > 18e4) return false;
339
+ const diff = parseInt(diffStr, 10);
340
+ if (diff < minDifficulty) return false;
341
+ const hash = (0, import_node_crypto.createHash)("sha256").update(`${seed}:${nonce}`).digest("hex");
342
+ return hash.startsWith("0".repeat(diff));
343
+ } catch {
344
+ return false;
345
+ }
346
+ }
347
+ function generateChallengeHtml(seed, difficulty, returnUrl = "/") {
348
+ return `<!DOCTYPE html>
349
+ <html lang="en">
350
+ <head>
351
+ <meta charset="utf-8">
352
+ <meta name="viewport" content="width=device-width, initial-scale=1">
353
+ <title>Verifying Connection...</title>
354
+ <style>
355
+ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #fafafa; color: #222; }
356
+ .card { background: #fff; border: 1px solid #e0e0e0; border-radius: 8px; padding: 32px; max-width: 400px; text-align: center; box-shadow: 0 4px 12px rgba(0,0,0,0.05); }
357
+ .spinner { border: 3px solid #f3f3f3; border-top: 3px solid #111; border-radius: 50%; width: 32px; height: 32px; animation: spin 0.8s linear infinite; margin: 20px auto; }
358
+ @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
359
+ h2 { font-size: 18px; margin-bottom: 8px; }
360
+ p { font-size: 14px; color: #666; margin: 0; }
361
+ </style>
362
+ </head>
363
+ <body>
364
+ <div class="card">
365
+ <h2>Verifying Connection</h2>
366
+ <p>Please wait a moment while your browser verifies your session...</p>
367
+ <div class="spinner"></div>
368
+ </div>
369
+ <script>
370
+ (async function() {
371
+ const seed = "${seed}";
372
+ const difficulty = ${difficulty};
373
+ const returnUrl = "${returnUrl}";
374
+ const targetPrefix = "0".repeat(difficulty);
375
+
376
+ // Automated browser checks
377
+ const isAutomated = !!(navigator.webdriver || window._phantom || window.__nightmare || !window.chrome);
378
+
379
+ async function sha256(message) {
380
+ const msgBuffer = new TextEncoder().encode(message);
381
+ const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
382
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
383
+ return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
384
+ }
385
+
386
+ let nonce = 0;
387
+ while (true) {
388
+ const hash = await sha256(seed + ":" + nonce);
389
+ if (hash.startsWith(targetPrefix)) break;
390
+ nonce++;
391
+ if (nonce > 500000) break;
392
+ }
393
+
394
+ try {
395
+ const res = await fetch("/__trafficguard/verify", {
396
+ method: "POST",
397
+ headers: { "Content-Type": "application/json" },
398
+ body: JSON.stringify({ seed, nonce: String(nonce), isAutomated, returnUrl })
399
+ });
400
+ if (res.ok) {
401
+ window.location.href = returnUrl;
402
+ } else {
403
+ document.querySelector("p").innerText = "Verification challenge failed.";
404
+ }
405
+ } catch (e) {
406
+ window.location.reload();
407
+ }
408
+ })();
409
+ </script>
410
+ </body>
411
+ </html>`;
412
+ }
413
+
414
+ // src/normalizer.ts
415
+ var KNOWN_GOOD_BOTS = [
416
+ "googlebot",
417
+ "bingbot",
418
+ "yandexbot",
419
+ "duckduckbot",
420
+ "slurp",
421
+ "baiduspider",
422
+ "facebookexternalhit",
423
+ "twitterbot",
424
+ "linkedinbot",
425
+ "embedly",
426
+ "quora link preview",
427
+ "showyoubot",
428
+ "outbrain",
429
+ "pinterest",
430
+ "slackbot",
431
+ "vkshare",
432
+ "w3c_validator",
433
+ "uptime"
434
+ ];
435
+ var KNOWN_ATTACK_TOOLS = [
436
+ "sqlmap",
437
+ "nikto",
438
+ "nmap",
439
+ "masscan",
440
+ "acunetix",
441
+ "dirbuster",
442
+ "gobuster",
443
+ "wfuzz",
444
+ "burpcollaborator",
445
+ "hydra",
446
+ "havij"
447
+ ];
448
+ function checkHeaderOrderAnomaly(rawHeaders, userAgent) {
449
+ if (!rawHeaders || rawHeaders.length < 6) return false;
450
+ const order = [];
451
+ for (let i = 0; i < rawHeaders.length; i += 2) {
452
+ order.push(rawHeaders[i].toLowerCase());
453
+ }
454
+ const ua = userAgent.toLowerCase();
455
+ const claimsChromium = ua.includes("chrome/") || ua.includes("edg/");
456
+ const hostIdx = order.indexOf("host");
457
+ const uaIdx = order.indexOf("user-agent");
458
+ const secChUaIdx = order.indexOf("sec-ch-ua");
459
+ if (uaIdx !== -1 && hostIdx !== -1 && uaIdx < hostIdx) {
460
+ return true;
461
+ }
462
+ if (claimsChromium && secChUaIdx !== -1 && uaIdx !== -1) {
463
+ const acceptEncIdx = order.indexOf("accept-encoding");
464
+ if (acceptEncIdx !== -1 && secChUaIdx > acceptEncIdx + 2) {
465
+ return true;
466
+ }
467
+ }
468
+ return false;
469
+ }
470
+ function normalizeRequest(input) {
471
+ let method = "GET";
472
+ let path = "/";
473
+ let query = {};
474
+ const headers = {};
475
+ let rawHeaders;
476
+ let body = null;
477
+ let ip;
478
+ let cookies = {};
479
+ if (typeof input === "string") {
480
+ try {
481
+ const parsed = new URL(input, "http://localhost");
482
+ path = parsed.pathname;
483
+ parsed.searchParams.forEach((v, k) => {
484
+ query[k] = v;
485
+ });
486
+ } catch {
487
+ path = input;
488
+ }
489
+ } else if (typeof input === "object" && input !== null) {
490
+ const raw = input;
491
+ method = (raw.method || "GET").toUpperCase();
492
+ const rawUrl = raw.url || raw.originalUrl || raw.path || "/";
493
+ try {
494
+ const parsed = new URL(rawUrl, "http://localhost");
495
+ path = parsed.pathname;
496
+ parsed.searchParams.forEach((v, k) => {
497
+ query[k] = v;
498
+ });
499
+ } catch {
500
+ path = typeof rawUrl === "string" ? rawUrl : "/";
501
+ }
502
+ if (raw.query && typeof raw.query === "object") {
503
+ query = { ...query, ...raw.query };
504
+ }
505
+ if (raw.headers && typeof raw.headers === "object") {
506
+ for (const [k, v] of Object.entries(raw.headers)) {
507
+ if (typeof v === "string") {
508
+ headers[k.toLowerCase()] = v;
509
+ } else if (Array.isArray(v)) {
510
+ headers[k.toLowerCase()] = v.join(", ");
511
+ }
512
+ }
513
+ }
514
+ rawHeaders = raw.rawHeaders;
515
+ body = raw.body ?? null;
516
+ ip = raw.ip || raw.socket?.remoteAddress || headers["x-forwarded-for"]?.split(",")[0]?.trim();
517
+ cookies = raw.cookies || parseCookies(headers["cookie"]);
518
+ }
519
+ const userAgent = (headers["user-agent"] || "").toLowerCase();
520
+ const claimsBrowser = userAgent.includes("mozilla/") && (userAgent.includes("chrome/") || userAgent.includes("safari/") || userAgent.includes("firefox/") || userAgent.includes("edg/"));
521
+ const isKnownSearchBot = KNOWN_GOOD_BOTS.some((bot) => userAgent.includes(bot));
522
+ const missingBrowserHeaders = [];
523
+ if (claimsBrowser && !isKnownSearchBot) {
524
+ if (!headers["accept-language"]) missingBrowserHeaders.push("accept-language");
525
+ if (!headers["accept-encoding"]) missingBrowserHeaders.push("accept-encoding");
526
+ const isChromium = userAgent.includes("chrome/") || userAgent.includes("edg/");
527
+ if (isChromium && !headers["sec-ch-ua"]) {
528
+ missingBrowserHeaders.push("sec-ch-ua");
529
+ }
530
+ }
531
+ const suspiciousSignatures = [];
532
+ for (const tool of KNOWN_ATTACK_TOOLS) {
533
+ if (userAgent.includes(tool)) {
534
+ suspiciousSignatures.push(`known_attack_tool:${tool}`);
535
+ }
536
+ }
537
+ const headerOrderAnomaly = checkHeaderOrderAnomaly(rawHeaders, userAgent);
538
+ const targetForEntropy = path + " " + JSON.stringify(query);
539
+ const shannonEntropy = calculateShannonEntropy(targetForEntropy);
540
+ const normalized = {
541
+ method,
542
+ path,
543
+ query,
544
+ headers,
545
+ rawHeaders,
546
+ body,
547
+ ip,
548
+ url: path,
549
+ cookies
550
+ };
551
+ const context = {
552
+ missingBrowserHeaders,
553
+ suspiciousSignatures,
554
+ claimsBrowser,
555
+ isKnownSearchBot,
556
+ headerOrderAnomaly,
557
+ shannonEntropy
558
+ };
559
+ return { request: normalized, context };
560
+ }
561
+
562
+ // src/policy.ts
563
+ var DEFAULT_POLICIES = {
564
+ balanced: {
565
+ attackThreshold: 0.75,
566
+ botThreshold: 0.85,
567
+ challengeThreshold: 0.5,
568
+ riskBlock: 2.2,
569
+ riskChallenge: 1.4,
570
+ spoofThreshold: 0.6,
571
+ velocityBurstLimit: 40
572
+ // requests per 10s window
573
+ },
574
+ strict: {
575
+ attackThreshold: 0.6,
576
+ botThreshold: 0.7,
577
+ challengeThreshold: 0.35,
578
+ riskBlock: 1.8,
579
+ riskChallenge: 1,
580
+ spoofThreshold: 0.4,
581
+ velocityBurstLimit: 20
582
+ },
583
+ permissive: {
584
+ attackThreshold: 0.88,
585
+ botThreshold: 0.92,
586
+ challengeThreshold: 0.7,
587
+ riskBlock: 2.6,
588
+ riskChallenge: 2,
589
+ spoofThreshold: 0.75,
590
+ velocityBurstLimit: 80
591
+ }
592
+ };
593
+ function resolvePolicy(policy) {
594
+ if (typeof policy === "string") {
595
+ return DEFAULT_POLICIES[policy] || DEFAULT_POLICIES.balanced;
596
+ }
597
+ if (typeof policy === "object" && policy !== null) {
598
+ return {
599
+ ...DEFAULT_POLICIES.balanced,
600
+ ...policy
601
+ };
602
+ }
603
+ return DEFAULT_POLICIES.balanced;
604
+ }
605
+ function evaluateDecision(assessment, policy, options, durationMs, context) {
606
+ const allowGoodBots = options.allowGoodBots !== false;
607
+ const reasons = [];
608
+ if (context?.isHoneypot) {
609
+ return {
610
+ action: "block",
611
+ shouldBlock: true,
612
+ shouldChallenge: false,
613
+ shouldTarpit: false,
614
+ isBot: true,
615
+ isAttack: true,
616
+ category: "attack",
617
+ riskScore: 3,
618
+ confidence: 1,
619
+ reasons: ["Visited canary honeypot URL trap intended for crawlers"],
620
+ assessment: {
621
+ ...assessment,
622
+ isAttackProbability: 1,
623
+ isBotProbability: 1,
624
+ riskScore: 3
625
+ },
626
+ durationMs
627
+ };
628
+ }
629
+ if (allowGoodBots && assessment.trafficType === "good_bot" && assessment.isAttackProbability < 0.4) {
630
+ return {
631
+ action: "allow",
632
+ shouldBlock: false,
633
+ shouldChallenge: false,
634
+ shouldTarpit: false,
635
+ isBot: true,
636
+ isAttack: false,
637
+ category: "good_bot",
638
+ riskScore: assessment.riskScore,
639
+ confidence: assessment.trafficTypeConfidence,
640
+ reasons: ["Verified legitimate crawler allowed by policy"],
641
+ assessment,
642
+ durationMs
643
+ };
644
+ }
645
+ let action = "allow";
646
+ if (assessment.isAttackProbability >= policy.attackThreshold) {
647
+ action = "block";
648
+ reasons.push(
649
+ `Detected malicious attack payload or exploit attempt (probability: ${Math.round(assessment.isAttackProbability * 100)}%)`
650
+ );
651
+ }
652
+ if (assessment.riskScore >= policy.riskBlock) {
653
+ action = "block";
654
+ reasons.push(`High risk severity score (${assessment.riskScore.toFixed(2)} >= ${policy.riskBlock})`);
655
+ } else if (assessment.riskScore >= policy.riskChallenge && action !== "block") {
656
+ action = "challenge";
657
+ reasons.push(`Elevated risk severity score (${assessment.riskScore.toFixed(2)} >= ${policy.riskChallenge})`);
658
+ }
659
+ if (context?.rateCount && context.rateCount > policy.velocityBurstLimit) {
660
+ if (action !== "block") {
661
+ action = "tarpit";
662
+ reasons.push(
663
+ `High request burst velocity (${context.rateCount} reqs in window > ${policy.velocityBurstLimit} limit)`
664
+ );
665
+ }
666
+ }
667
+ if (assessment.isBotProbability >= policy.botThreshold && assessment.trafficType === "bad_bot") {
668
+ if (action !== "block") {
669
+ action = "block";
670
+ reasons.push(
671
+ `Unapproved automated bot/scraper detected (probability: ${Math.round(assessment.isBotProbability * 100)}%)`
672
+ );
673
+ }
674
+ } else if (assessment.isBotProbability >= policy.challengeThreshold && action === "allow") {
675
+ action = "challenge";
676
+ reasons.push(
677
+ `Possible automated traffic requiring verification (probability: ${Math.round(assessment.isBotProbability * 100)}%)`
678
+ );
679
+ }
680
+ if (context?.headerOrderAnomaly) {
681
+ reasons.push("Abnormal HTTP header sequence inconsistent with declared browser");
682
+ if (action === "allow") {
683
+ action = "challenge";
684
+ }
685
+ }
686
+ if (assessment.isSpoofedProbability >= policy.spoofThreshold && action === "allow") {
687
+ action = "challenge";
688
+ reasons.push(
689
+ `Suspicious header inconsistency or impersonation detected (probability: ${Math.round(assessment.isSpoofedProbability * 100)}%)`
690
+ );
691
+ }
692
+ if (action === "allow" && reasons.length === 0) {
693
+ reasons.push("Traffic matches expected legitimate profile");
694
+ }
695
+ const shouldBlock = action === "block";
696
+ const shouldChallenge = action === "challenge";
697
+ const shouldTarpit = action === "tarpit";
698
+ const isBot = assessment.isBotProbability >= 0.5;
699
+ const isAttack = assessment.isAttackProbability >= 0.5;
700
+ const decision = {
701
+ action,
702
+ shouldBlock,
703
+ shouldChallenge,
704
+ shouldTarpit,
705
+ isBot,
706
+ isAttack,
707
+ category: assessment.trafficType,
708
+ riskScore: assessment.riskScore,
709
+ confidence: assessment.trafficTypeConfidence,
710
+ reasons,
711
+ assessment,
712
+ durationMs,
713
+ async respond(res) {
714
+ if (!res || typeof res.status !== "function") return false;
715
+ if (shouldTarpit) {
716
+ if (options.onTarpit) {
717
+ options.onTarpit(decision, null, res);
718
+ } else {
719
+ const delay = options.tarpitMs || 3e3;
720
+ await new Promise((r) => setTimeout(r, delay));
721
+ res.status(429).json({
722
+ error: "Too Many Requests",
723
+ message: "Request velocity exceeded threshold",
724
+ action: "tarpit",
725
+ reasons: decision.reasons
726
+ });
727
+ }
728
+ return true;
729
+ }
730
+ if (shouldBlock) {
731
+ if (options.onBlock) {
732
+ options.onBlock(decision, null, res);
733
+ } else {
734
+ res.status(403).json({
735
+ error: "Forbidden",
736
+ message: "Access blocked by BotGate security policy",
737
+ reasons: decision.reasons
738
+ });
739
+ }
740
+ return true;
741
+ }
742
+ if (shouldChallenge) {
743
+ if (options.onChallenge) {
744
+ options.onChallenge(decision, null, res);
745
+ } else if (options.enablePoWChallenge !== false && decision.challengeHtml) {
746
+ res.status(428);
747
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
748
+ res.end(decision.challengeHtml);
749
+ } else {
750
+ res.status(428).json({
751
+ error: "Precondition Required",
752
+ message: "Traffic verification or challenge required",
753
+ reasons: decision.reasons
754
+ });
755
+ }
756
+ return true;
757
+ }
758
+ return false;
759
+ }
760
+ };
761
+ return decision;
762
+ }
763
+
764
+ // src/gate.ts
765
+ var sdkLoaded2 = false;
766
+ var TypeSafeClientClass = null;
767
+ async function getTypeSafeClientClass() {
768
+ if (!sdkLoaded2) {
769
+ try {
770
+ const sdk = await import("@typesafe-ai/sdk");
771
+ TypeSafeClientClass = sdk.TypeSafeClient;
772
+ await loadTypeSafePrimitives();
773
+ } catch {
774
+ TypeSafeClientClass = null;
775
+ }
776
+ sdkLoaded2 = true;
777
+ }
778
+ return TypeSafeClientClass;
779
+ }
780
+ var BotGate = class {
781
+ client = null;
782
+ clientInitAttempted = false;
783
+ options;
784
+ battery = createBotGateBattery();
785
+ constructor(options = {}) {
786
+ this.options = options;
787
+ }
788
+ async getClient() {
789
+ if (!this.clientInitAttempted) {
790
+ this.clientInitAttempted = true;
791
+ const apiKey = this.options.apiKey || process.env.TYPESAFE_API_KEY;
792
+ if (apiKey) {
793
+ const ClientClass = await getTypeSafeClientClass();
794
+ if (ClientClass) {
795
+ this.client = new ClientClass({
796
+ apiKey,
797
+ baseURL: this.options.endpoint || process.env.TYPESAFE_ENDPOINT,
798
+ timeout: this.options.timeout || 15e3
799
+ });
800
+ this.battery = createBotGateBattery();
801
+ }
802
+ }
803
+ }
804
+ return this.client;
805
+ }
806
+ async inspect(input, runtimeOptions = {}) {
807
+ const startTime = performance.now();
808
+ const opts = { ...this.options, ...runtimeOptions };
809
+ const policy = resolvePolicy(opts.policy);
810
+ const { request, context } = normalizeRequest(input);
811
+ if (opts.whitelistedPaths) {
812
+ for (const pattern of opts.whitelistedPaths) {
813
+ if (typeof pattern === "string" && request.path === pattern) {
814
+ return this.createAllowedDecision("Path whitelisted", startTime);
815
+ }
816
+ if (pattern instanceof RegExp && pattern.test(request.path)) {
817
+ return this.createAllowedDecision("Path matched whitelist pattern", startTime);
818
+ }
819
+ }
820
+ }
821
+ if (opts.whitelistedIps && request.ip && opts.whitelistedIps.includes(request.ip)) {
822
+ return this.createAllowedDecision("Client IP whitelisted", startTime);
823
+ }
824
+ if (opts.honeypotPaths && opts.honeypotPaths.includes(request.path)) {
825
+ context.isHoneypot = true;
826
+ }
827
+ const secret = opts.secretKey || process.env.TRAFFICGUARD_SECRET || process.env.BOTGATE_SECRET || "traffic-guard-default-secret-key-32b!";
828
+ const userAgent = request.headers["user-agent"] || "";
829
+ const clientHash = createClientHash(request.ip, userAgent);
830
+ const rawCookie = request.cookies?.["__trafficguard"] || request.cookies?.["__botgate"];
831
+ const now = Date.now();
832
+ let cookiePayload = null;
833
+ if (rawCookie) {
834
+ cookiePayload = verifyBotToken(rawCookie, secret);
835
+ }
836
+ if (cookiePayload && cookiePayload.h === clientHash && cookiePayload.v && cookiePayload.v > now) {
837
+ return this.createAllowedDecision("Client passed cryptographic verification", startTime);
838
+ }
839
+ let currentCount = 1;
840
+ let windowStart = now;
841
+ if (cookiePayload && cookiePayload.h === clientHash) {
842
+ if (now - cookiePayload.ws < 1e4) {
843
+ currentCount = cookiePayload.c + 1;
844
+ windowStart = cookiePayload.ws;
845
+ }
846
+ }
847
+ context.rateCount = currentCount;
848
+ let assessment;
849
+ const client = await this.getClient();
850
+ if (client && !context.isHoneypot) {
851
+ try {
852
+ const state = {
853
+ request: {
854
+ method: request.method,
855
+ path: request.path,
856
+ headers: request.headers,
857
+ query: request.query,
858
+ body: request.body ?? null,
859
+ ip: request.ip ?? null
860
+ },
861
+ context: {
862
+ missing_browser_headers: context.missingBrowserHeaders,
863
+ suspicious_signatures: context.suspiciousSignatures,
864
+ claims_browser: context.claimsBrowser,
865
+ is_known_search_bot: context.isKnownSearchBot,
866
+ header_order_anomaly: context.headerOrderAnomaly,
867
+ shannon_entropy: context.shannonEntropy,
868
+ rate_count: context.rateCount
869
+ }
870
+ };
871
+ const response = await client.systemOne({
872
+ model: opts.model || "jev-latest",
873
+ state,
874
+ questions: this.battery
875
+ });
876
+ const answers = response.answers;
877
+ const isBotProb = answers.is_bot?.noul ?? 0.1;
878
+ const isAttackProb = answers.is_attack?.noul ?? 0.05;
879
+ const isSpoofedProb = answers.is_spoofed?.noul ?? 0.05;
880
+ const trafficTypeAnswer = answers.traffic_type;
881
+ const trafficType = trafficTypeAnswer?.choice || "human";
882
+ const trafficTypeConfidence = trafficTypeAnswer?.confidence ?? 0.8;
883
+ const trafficTypeDist = trafficTypeAnswer?.probabilities ?? {};
884
+ const riskAnswer = answers.risk_level;
885
+ const riskScore = riskAnswer?.score ?? 0;
886
+ const riskConfidence = riskAnswer?.confidence ?? 0.8;
887
+ const riskDist = riskAnswer?.probabilities ?? {};
888
+ assessment = {
889
+ isBotProbability: isBotProb,
890
+ isAttackProbability: isAttackProb,
891
+ isSpoofedProbability: isSpoofedProb,
892
+ trafficType,
893
+ trafficTypeConfidence,
894
+ trafficTypeDistribution: trafficTypeDist,
895
+ riskScore,
896
+ riskConfidence,
897
+ riskLevelDistribution: riskDist
898
+ };
899
+ } catch (err) {
900
+ if (opts.fallback === "block") {
901
+ return this.createBlockedDecision("TypeSafe API unavailable (fallback: block)", startTime);
902
+ }
903
+ if (opts.fallback === "allow") {
904
+ return this.createAllowedDecision("TypeSafe API unavailable (fallback: allow)", startTime);
905
+ }
906
+ assessment = heuristicAssessment(request, context);
907
+ }
908
+ } else {
909
+ assessment = heuristicAssessment(request, context);
910
+ }
911
+ const durationMs = Math.round((performance.now() - startTime) * 100) / 100;
912
+ const decision = evaluateDecision(assessment, policy, opts, durationMs, context);
913
+ const updatedPayload = {
914
+ h: clientHash,
915
+ c: currentCount,
916
+ ws: windowStart
917
+ };
918
+ const newToken = signBotToken(updatedPayload, secret);
919
+ decision.setCookieHeader = `__trafficguard=${newToken}; Path=/; HttpOnly; SameSite=Lax; Max-Age=3600`;
920
+ if (decision.shouldChallenge) {
921
+ const pow = createPoWChallenge(secret, 4);
922
+ decision.challengeHtml = generateChallengeHtml(pow.seed, pow.difficulty, request.url || "/");
923
+ }
924
+ return decision;
925
+ }
926
+ middleware(middlewareOptions = {}) {
927
+ const opts = { ...this.options, ...middlewareOptions };
928
+ const secret = opts.secretKey || process.env.TRAFFICGUARD_SECRET || process.env.BOTGATE_SECRET || "traffic-guard-default-secret-key-32b!";
929
+ return async (req, res, next) => {
930
+ try {
931
+ if (req.method === "POST" && (req.url === "/__trafficguard/verify" || req.path === "/__trafficguard/verify" || req.url === "/__botgate/verify" || req.path === "/__botgate/verify")) {
932
+ let body = req.body;
933
+ if (typeof body === "string") {
934
+ try {
935
+ body = JSON.parse(body);
936
+ } catch {
937
+ }
938
+ }
939
+ const { seed, nonce, isAutomated, returnUrl } = body || {};
940
+ const isValid = !isAutomated && verifyPoW(seed, nonce, 1, secret);
941
+ if (isValid) {
942
+ const userAgent = req.headers["user-agent"] || "";
943
+ const ip = req.ip || req.socket?.remoteAddress || "";
944
+ const clientHash = createClientHash(ip, userAgent);
945
+ const token = signBotToken({ h: clientHash, c: 1, ws: Date.now(), v: Date.now() + 18e5 }, secret);
946
+ res.setHeader("Set-Cookie", `__trafficguard=${token}; Path=/; HttpOnly; SameSite=Lax; Max-Age=1800`);
947
+ return res.status(200).json({ status: "ok", redirect: returnUrl || "/" });
948
+ } else {
949
+ return res.status(403).json({ error: "Verification failed" });
950
+ }
951
+ }
952
+ const decision = await this.inspect(req, opts);
953
+ req.trafficGuard = decision;
954
+ req.botGate = decision;
955
+ if (decision.setCookieHeader) {
956
+ res.setHeader("Set-Cookie", decision.setCookieHeader);
957
+ }
958
+ if (decision.shouldTarpit) {
959
+ if (opts.onTarpit) {
960
+ return opts.onTarpit(decision, req, res);
961
+ }
962
+ const delay = opts.tarpitMs || 3e3;
963
+ await new Promise((r) => setTimeout(r, delay));
964
+ return res.status(429).json({
965
+ error: "Too Many Requests",
966
+ message: "Velocity exceeded threshold",
967
+ action: decision.action,
968
+ reasons: decision.reasons
969
+ });
970
+ }
971
+ if (decision.shouldBlock) {
972
+ if (opts.onBlock) {
973
+ return opts.onBlock(decision, req, res);
974
+ }
975
+ return res.status(403).json({
976
+ error: "Forbidden",
977
+ message: "Access blocked by BotGate security policy",
978
+ action: decision.action,
979
+ reasons: decision.reasons,
980
+ category: decision.category,
981
+ riskScore: decision.riskScore
982
+ });
983
+ }
984
+ if (decision.shouldChallenge) {
985
+ if (opts.onChallenge) {
986
+ return opts.onChallenge(decision, req, res);
987
+ }
988
+ if (opts.enablePoWChallenge !== false && decision.challengeHtml) {
989
+ res.status(428);
990
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
991
+ return res.end(decision.challengeHtml);
992
+ }
993
+ res.setHeader("X-BotGate-Action", "challenge");
994
+ res.setHeader("X-BotGate-Risk", decision.riskScore.toString());
995
+ } else {
996
+ res.setHeader("X-BotGate-Action", "allow");
997
+ }
998
+ next();
999
+ } catch (err) {
1000
+ next(err);
1001
+ }
1002
+ };
1003
+ }
1004
+ createAllowedDecision(reason, startTime) {
1005
+ const durationMs = Math.round((performance.now() - startTime) * 100) / 100;
1006
+ return {
1007
+ action: "allow",
1008
+ shouldBlock: false,
1009
+ shouldChallenge: false,
1010
+ shouldTarpit: false,
1011
+ isBot: false,
1012
+ isAttack: false,
1013
+ category: "human",
1014
+ riskScore: 0,
1015
+ confidence: 1,
1016
+ reasons: [reason],
1017
+ assessment: {
1018
+ isBotProbability: 0,
1019
+ isAttackProbability: 0,
1020
+ isSpoofedProbability: 0,
1021
+ trafficType: "human",
1022
+ trafficTypeConfidence: 1,
1023
+ trafficTypeDistribution: { human: 1 },
1024
+ riskScore: 0,
1025
+ riskConfidence: 1,
1026
+ riskLevelDistribution: { "0": 1 }
1027
+ },
1028
+ durationMs,
1029
+ respond: () => false
1030
+ };
1031
+ }
1032
+ createBlockedDecision(reason, startTime) {
1033
+ const durationMs = Math.round((performance.now() - startTime) * 100) / 100;
1034
+ return {
1035
+ action: "block",
1036
+ shouldBlock: true,
1037
+ shouldChallenge: false,
1038
+ shouldTarpit: false,
1039
+ isBot: true,
1040
+ isAttack: true,
1041
+ category: "attack",
1042
+ riskScore: 3,
1043
+ confidence: 1,
1044
+ reasons: [reason],
1045
+ assessment: {
1046
+ isBotProbability: 1,
1047
+ isAttackProbability: 1,
1048
+ isSpoofedProbability: 1,
1049
+ trafficType: "attack",
1050
+ trafficTypeConfidence: 1,
1051
+ trafficTypeDistribution: { attack: 1 },
1052
+ riskScore: 3,
1053
+ riskConfidence: 1,
1054
+ riskLevelDistribution: { "3": 1 }
1055
+ },
1056
+ durationMs,
1057
+ respond: (res) => {
1058
+ if (res && typeof res.status === "function") {
1059
+ res.status(403).json({ error: "Forbidden", message: reason });
1060
+ return true;
1061
+ }
1062
+ return false;
1063
+ }
1064
+ };
1065
+ }
1066
+ };
1067
+ var TrafficGuard = BotGate;
1068
+
1069
+ // src/index.ts
1070
+ var defaultGuard = null;
1071
+ function getDefaultGuard() {
1072
+ if (!defaultGuard) {
1073
+ defaultGuard = new TrafficGuard();
1074
+ }
1075
+ return defaultGuard;
1076
+ }
1077
+ async function trafficguard(input, options) {
1078
+ if (options) {
1079
+ const guard = new TrafficGuard(options);
1080
+ return guard.inspect(input);
1081
+ }
1082
+ return getDefaultGuard().inspect(input);
1083
+ }
1084
+ trafficguard.create = (options) => new TrafficGuard(options);
1085
+ trafficguard.inspect = (input, options) => {
1086
+ return trafficguard(input, options);
1087
+ };
1088
+ trafficguard.middleware = (options) => {
1089
+ const guard = options ? new TrafficGuard(options) : getDefaultGuard();
1090
+ return guard.middleware(options);
1091
+ };
1092
+ trafficguard.TrafficGuard = TrafficGuard;
1093
+ trafficguard.BotGate = BotGate;
1094
+ var botgate = trafficguard;
1095
+ var trafficGuard = trafficguard;
1096
+ var index_default = trafficguard;
1097
+ // Annotate the CommonJS export names for ESM import in node:
1098
+ 0 && (module.exports = {
1099
+ BotGate,
1100
+ DEFAULT_POLICIES,
1101
+ TrafficGuard,
1102
+ botgate,
1103
+ calculateShannonEntropy,
1104
+ checkHeaderOrderAnomaly,
1105
+ createBotGateBattery,
1106
+ createClientHash,
1107
+ createPoWChallenge,
1108
+ evaluateDecision,
1109
+ generateChallengeHtml,
1110
+ heuristicAssessment,
1111
+ normalizeRequest,
1112
+ resolvePolicy,
1113
+ signBotToken,
1114
+ trafficGuard,
1115
+ trafficguard,
1116
+ verifyBotToken,
1117
+ verifyPoW
1118
+ });
1119
+ //# sourceMappingURL=index.cjs.map