@kya-os/agentshield-nextjs 0.1.40 → 0.1.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/.tsbuildinfo +1 -0
  2. package/dist/api-client.d.mts +145 -0
  3. package/dist/api-client.d.ts +145 -0
  4. package/dist/api-client.js +130 -0
  5. package/dist/api-client.js.map +1 -0
  6. package/dist/api-client.mjs +126 -0
  7. package/dist/api-client.mjs.map +1 -0
  8. package/dist/api-middleware.d.mts +118 -0
  9. package/dist/api-middleware.d.ts +118 -0
  10. package/dist/api-middleware.js +295 -0
  11. package/dist/api-middleware.js.map +1 -0
  12. package/dist/api-middleware.mjs +292 -0
  13. package/dist/api-middleware.mjs.map +1 -0
  14. package/dist/create-middleware.d.mts +2 -1
  15. package/dist/create-middleware.d.ts +2 -1
  16. package/dist/create-middleware.js +474 -200
  17. package/dist/create-middleware.js.map +1 -1
  18. package/dist/create-middleware.mjs +454 -200
  19. package/dist/create-middleware.mjs.map +1 -1
  20. package/dist/edge/index.d.mts +110 -0
  21. package/dist/edge/index.d.ts +110 -0
  22. package/dist/edge/index.js +253 -0
  23. package/dist/edge/index.js.map +1 -0
  24. package/dist/edge/index.mjs +251 -0
  25. package/dist/edge/index.mjs.map +1 -0
  26. package/dist/edge-detector-wrapper.d.mts +6 -15
  27. package/dist/edge-detector-wrapper.d.ts +6 -15
  28. package/dist/edge-detector-wrapper.js +314 -95
  29. package/dist/edge-detector-wrapper.js.map +1 -1
  30. package/dist/edge-detector-wrapper.mjs +294 -95
  31. package/dist/edge-detector-wrapper.mjs.map +1 -1
  32. package/dist/edge-runtime-loader.d.mts +1 -1
  33. package/dist/edge-runtime-loader.d.ts +1 -1
  34. package/dist/edge-runtime-loader.js +10 -25
  35. package/dist/edge-runtime-loader.js.map +1 -1
  36. package/dist/edge-runtime-loader.mjs +11 -23
  37. package/dist/edge-runtime-loader.mjs.map +1 -1
  38. package/dist/edge-wasm-middleware.js +2 -1
  39. package/dist/edge-wasm-middleware.js.map +1 -1
  40. package/dist/edge-wasm-middleware.mjs +2 -1
  41. package/dist/edge-wasm-middleware.mjs.map +1 -1
  42. package/dist/enhanced-middleware.d.mts +153 -0
  43. package/dist/enhanced-middleware.d.ts +153 -0
  44. package/dist/enhanced-middleware.js +1074 -0
  45. package/dist/enhanced-middleware.js.map +1 -0
  46. package/dist/enhanced-middleware.mjs +1072 -0
  47. package/dist/enhanced-middleware.mjs.map +1 -0
  48. package/dist/index.d.mts +8 -153
  49. package/dist/index.d.ts +8 -153
  50. package/dist/index.js +1390 -680
  51. package/dist/index.js.map +1 -1
  52. package/dist/index.mjs +1370 -685
  53. package/dist/index.mjs.map +1 -1
  54. package/dist/middleware.d.mts +2 -1
  55. package/dist/middleware.d.ts +2 -1
  56. package/dist/middleware.js +474 -200
  57. package/dist/middleware.js.map +1 -1
  58. package/dist/middleware.mjs +454 -200
  59. package/dist/middleware.mjs.map +1 -1
  60. package/dist/session-tracker.d.mts +1 -1
  61. package/dist/session-tracker.d.ts +1 -1
  62. package/dist/session-tracker.js.map +1 -1
  63. package/dist/session-tracker.mjs.map +1 -1
  64. package/dist/signature-verifier.d.mts +1 -0
  65. package/dist/signature-verifier.d.ts +1 -0
  66. package/dist/signature-verifier.js +204 -44
  67. package/dist/signature-verifier.js.map +1 -1
  68. package/dist/signature-verifier.mjs +184 -44
  69. package/dist/signature-verifier.mjs.map +1 -1
  70. package/dist/{types-BJTEUa4T.d.mts → types-DVmy9NE3.d.mts} +19 -2
  71. package/dist/{types-BJTEUa4T.d.ts → types-DVmy9NE3.d.ts} +19 -2
  72. package/dist/wasm-middleware.js +15 -6
  73. package/dist/wasm-middleware.js.map +1 -1
  74. package/dist/wasm-middleware.mjs +15 -6
  75. package/dist/wasm-middleware.mjs.map +1 -1
  76. package/package.json +43 -21
  77. package/wasm/agentshield_wasm.js +209 -152
  78. package/wasm/agentshield_wasm_bg.wasm +0 -0
  79. package/wasm/package.json +30 -0
@@ -1,19 +1,129 @@
1
1
  import { NextResponse } from 'next/server';
2
+ import * as ed25519 from '@noble/ed25519';
3
+ import { sha512 } from '@noble/hashes/sha2';
4
+ import { loadRulesSync, mapVerificationMethod } from '@kya-os/agentshield-shared';
2
5
 
3
6
  // src/create-middleware.ts
4
-
5
- // src/signature-verifier.ts
7
+ ed25519.etc.sha512Sync = (...m) => sha512(ed25519.etc.concatBytes(...m));
6
8
  var KNOWN_KEYS = {
7
9
  chatgpt: [
8
10
  {
9
11
  kid: "otMqcjr17mGyruktGvJU8oojQTSMHlVm7uO-lrcqbdg",
10
12
  // ChatGPT's current Ed25519 public key (base64)
13
+ // Source: https://chatgpt.com/.well-known/http-message-signatures-directory
11
14
  publicKey: "7F_3jDlxaquwh291MiACkcS3Opq88NksyHiakzS-Y1g",
12
- validFrom: (/* @__PURE__ */ new Date("2025-01-01")).getTime() / 1e3,
13
- validUntil: (/* @__PURE__ */ new Date("2025-04-11")).getTime() / 1e3
15
+ validFrom: 1735689600,
16
+ // Jan 1, 2025 (from OpenAI's nbf)
17
+ // Extended expiration as fallback safety - API fetch should provide fresh keys
18
+ // Check OpenAI's well-known endpoint for actual expiration dates
19
+ validUntil: 1799625600
20
+ // Jan 1, 2027 (extended for fallback safety)
14
21
  }
15
22
  ]
16
23
  };
24
+ var keyCache = /* @__PURE__ */ new Map();
25
+ var CACHE_TTL_MS = 5 * 60 * 1e3;
26
+ var CACHE_MAX_SIZE = 100;
27
+ function getApiBaseUrl() {
28
+ if (typeof window !== "undefined") {
29
+ return "/api/internal";
30
+ }
31
+ const baseUrl2 = process.env.NEXT_PUBLIC_APP_URL || process.env.NEXT_PUBLIC_API_URL || process.env.API_URL || (process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : null);
32
+ if (baseUrl2) {
33
+ return baseUrl2.replace(/\/$/, "") + "/api/internal";
34
+ }
35
+ if (process.env.NODE_ENV !== "production") {
36
+ console.warn(
37
+ "[Signature] No base URL configured for server-side fetch. Using localhost fallback."
38
+ );
39
+ return "http://localhost:3000/api/internal";
40
+ }
41
+ console.error(
42
+ "[Signature] CRITICAL: No base URL configured for server-side fetch in production!"
43
+ );
44
+ return "/api/internal";
45
+ }
46
+ function cleanupExpiredCache() {
47
+ const now = Date.now();
48
+ const entriesToDelete = [];
49
+ for (const [agent, cached] of keyCache.entries()) {
50
+ if (now - cached.cachedAt > CACHE_TTL_MS) {
51
+ entriesToDelete.push(agent);
52
+ }
53
+ }
54
+ for (const agent of entriesToDelete) {
55
+ keyCache.delete(agent);
56
+ }
57
+ if (keyCache.size > CACHE_MAX_SIZE) {
58
+ const entries = Array.from(keyCache.entries()).map(([agent, cached]) => ({
59
+ agent,
60
+ cachedAt: cached.cachedAt
61
+ }));
62
+ entries.sort((a, b) => a.cachedAt - b.cachedAt);
63
+ const toRemove = entries.slice(0, keyCache.size - CACHE_MAX_SIZE);
64
+ for (const entry of toRemove) {
65
+ keyCache.delete(entry.agent);
66
+ }
67
+ }
68
+ }
69
+ async function fetchKeysFromApi(agent) {
70
+ if (keyCache.size > CACHE_MAX_SIZE) {
71
+ cleanupExpiredCache();
72
+ }
73
+ const cached = keyCache.get(agent);
74
+ if (cached && Date.now() - cached.cachedAt < CACHE_TTL_MS) {
75
+ return cached.keys;
76
+ }
77
+ if (typeof fetch === "undefined") {
78
+ console.warn("[Signature] fetch() not available in this environment");
79
+ return null;
80
+ }
81
+ try {
82
+ const apiBaseUrl = getApiBaseUrl();
83
+ const url = `${apiBaseUrl}/signature-keys?agent=${encodeURIComponent(agent)}`;
84
+ const response = await fetch(url, {
85
+ method: "GET",
86
+ headers: {
87
+ "Content-Type": "application/json"
88
+ },
89
+ // 5 second timeout
90
+ signal: AbortSignal.timeout(5e3)
91
+ });
92
+ if (!response.ok) {
93
+ console.warn(`[Signature] Failed to fetch keys from API: ${response.status}`);
94
+ return null;
95
+ }
96
+ const data = await response.json();
97
+ if (!data.keys || !Array.isArray(data.keys) || data.keys.length === 0) {
98
+ console.warn(`[Signature] No keys returned from API for agent: ${agent}`);
99
+ return null;
100
+ }
101
+ keyCache.set(agent, {
102
+ keys: data.keys,
103
+ cachedAt: Date.now()
104
+ });
105
+ return data.keys;
106
+ } catch (error) {
107
+ console.warn("[Signature] Error fetching keys from API, using fallback", {
108
+ error: error instanceof Error ? error.message : "Unknown error",
109
+ agent
110
+ });
111
+ return null;
112
+ }
113
+ }
114
+ function isValidAgent(agent) {
115
+ return agent in KNOWN_KEYS;
116
+ }
117
+ async function getKeysForAgent(agent) {
118
+ const apiKeys = await fetchKeysFromApi(agent);
119
+ if (apiKeys && apiKeys.length > 0) {
120
+ return apiKeys;
121
+ }
122
+ if (isValidAgent(agent)) {
123
+ return KNOWN_KEYS[agent];
124
+ }
125
+ return [];
126
+ }
17
127
  function parseSignatureInput(signatureInput) {
18
128
  try {
19
129
  const match = signatureInput.match(/sig1=\((.*?)\);(.+)/);
@@ -49,21 +159,29 @@ function buildSignatureBase(method, path, headers, signedHeaders) {
49
159
  case "@authority":
50
160
  value = headers["host"] || headers["Host"] || "";
51
161
  break;
52
- default:
53
- const key = Object.keys(headers).find(
54
- (k) => k.toLowerCase() === headerName.toLowerCase()
55
- );
162
+ default: {
163
+ const key = Object.keys(headers).find((k) => k.toLowerCase() === headerName.toLowerCase());
56
164
  value = key ? headers[key] || "" : "";
57
165
  break;
166
+ }
58
167
  }
59
168
  components.push(`"${headerName}": ${value}`);
60
169
  }
61
170
  return components.join("\n");
62
171
  }
172
+ function base64ToBytes(base64) {
173
+ let standardBase64 = base64.replace(/-/g, "+").replace(/_/g, "/");
174
+ const padding = standardBase64.length % 4;
175
+ if (padding) {
176
+ standardBase64 += "=".repeat(4 - padding);
177
+ }
178
+ const binaryString = atob(standardBase64);
179
+ return Uint8Array.from(binaryString, (c) => c.charCodeAt(0));
180
+ }
63
181
  async function verifyEd25519Signature(publicKeyBase64, signatureBase64, message) {
64
182
  try {
65
- const publicKeyBytes = Uint8Array.from(atob(publicKeyBase64), (c) => c.charCodeAt(0));
66
- const signatureBytes = Uint8Array.from(atob(signatureBase64), (c) => c.charCodeAt(0));
183
+ const publicKeyBytes = base64ToBytes(publicKeyBase64);
184
+ const signatureBytes = base64ToBytes(signatureBase64);
67
185
  const messageBytes = new TextEncoder().encode(message);
68
186
  if (publicKeyBytes.length !== 32) {
69
187
  console.error("[Signature] Invalid public key length:", publicKeyBytes.length);
@@ -73,34 +191,36 @@ async function verifyEd25519Signature(publicKeyBase64, signatureBase64, message)
73
191
  console.error("[Signature] Invalid signature length:", signatureBytes.length);
74
192
  return false;
75
193
  }
76
- const publicKey = await crypto.subtle.importKey(
77
- "raw",
78
- publicKeyBytes,
79
- {
80
- name: "Ed25519",
81
- namedCurve: "Ed25519"
82
- },
83
- false,
84
- ["verify"]
85
- );
86
- const isValid = await crypto.subtle.verify(
87
- "Ed25519",
88
- publicKey,
89
- signatureBytes,
90
- messageBytes
91
- );
92
- return isValid;
93
- } catch (error) {
94
- console.error("[Signature] Ed25519 verification failed:", error);
95
- if (typeof window === "undefined") {
96
- try {
97
- console.warn("[Signature] Ed25519 not supported in this environment");
98
- return false;
99
- } catch {
100
- return false;
101
- }
194
+ return ed25519.verify(signatureBytes, messageBytes, publicKeyBytes);
195
+ } catch (nobleError) {
196
+ console.warn("[Signature] @noble/ed25519 failed, trying Web Crypto fallback:", nobleError);
197
+ try {
198
+ const publicKeyBytes = base64ToBytes(publicKeyBase64);
199
+ const signatureBytes = base64ToBytes(signatureBase64);
200
+ const messageBytes = new TextEncoder().encode(message);
201
+ const publicKey = await crypto.subtle.importKey(
202
+ "raw",
203
+ publicKeyBytes.buffer,
204
+ {
205
+ name: "Ed25519",
206
+ namedCurve: "Ed25519"
207
+ },
208
+ false,
209
+ ["verify"]
210
+ );
211
+ return await crypto.subtle.verify(
212
+ "Ed25519",
213
+ publicKey,
214
+ signatureBytes.buffer,
215
+ messageBytes
216
+ );
217
+ } catch (cryptoError) {
218
+ console.error("[Signature] Both @noble/ed25519 and Web Crypto failed:", {
219
+ nobleError: nobleError instanceof Error ? nobleError.message : "Unknown",
220
+ cryptoError: cryptoError instanceof Error ? cryptoError.message : "Unknown"
221
+ });
222
+ return false;
102
223
  }
103
- return false;
104
224
  }
105
225
  }
106
226
  async function verifyAgentSignature(method, path, headers) {
@@ -145,12 +265,12 @@ async function verifyAgentSignature(method, path, headers) {
145
265
  }
146
266
  }
147
267
  let agent;
148
- let knownKeys;
268
+ let agentKey;
149
269
  if (signatureAgent === '"https://chatgpt.com"' || signatureAgent?.includes("chatgpt.com")) {
150
270
  agent = "ChatGPT";
151
- knownKeys = KNOWN_KEYS.chatgpt;
271
+ agentKey = "chatgpt";
152
272
  }
153
- if (!agent || !knownKeys) {
273
+ if (!agent || !agentKey) {
154
274
  return {
155
275
  isValid: false,
156
276
  confidence: 0,
@@ -158,6 +278,15 @@ async function verifyAgentSignature(method, path, headers) {
158
278
  verificationMethod: "none"
159
279
  };
160
280
  }
281
+ const knownKeys = await getKeysForAgent(agentKey);
282
+ if (knownKeys.length === 0) {
283
+ return {
284
+ isValid: false,
285
+ confidence: 0,
286
+ reason: "No keys available for agent",
287
+ verificationMethod: "none"
288
+ };
289
+ }
161
290
  const key = knownKeys.find((k) => k.kid === parsed.keyid);
162
291
  if (!key) {
163
292
  return {
@@ -184,11 +313,7 @@ async function verifyAgentSignature(method, path, headers) {
184
313
  if (signatureValue.endsWith(":")) {
185
314
  signatureValue = signatureValue.slice(0, -1);
186
315
  }
187
- const isValid = await verifyEd25519Signature(
188
- key.publicKey,
189
- signatureValue,
190
- signatureBase
191
- );
316
+ const isValid = await verifyEd25519Signature(key.publicKey, signatureValue, signatureBase);
192
317
  if (isValid) {
193
318
  return {
194
319
  isValid: true,
@@ -212,27 +337,27 @@ function hasSignatureHeaders(headers) {
212
337
  }
213
338
  function isChatGPTSignature(headers) {
214
339
  const signatureAgent = headers["signature-agent"] || headers["Signature-Agent"];
215
- return signatureAgent === '"https://chatgpt.com"' || (signatureAgent?.includes("chatgpt.com") || false);
340
+ if (!signatureAgent) {
341
+ return false;
342
+ }
343
+ const agentUrlStr = signatureAgent.replace(/^"+|"+$/g, "");
344
+ if (agentUrlStr === "https://chatgpt.com") {
345
+ return true;
346
+ }
347
+ try {
348
+ const agentUrl = new URL(agentUrlStr);
349
+ const allowedHosts = ["chatgpt.com", "www.chatgpt.com"];
350
+ return allowedHosts.includes(agentUrl.host);
351
+ } catch {
352
+ return false;
353
+ }
216
354
  }
217
-
218
- // src/edge-detector-wrapper.ts
219
- var AI_AGENT_PATTERNS = [
220
- { pattern: /chatgpt-user/i, type: "chatgpt", name: "ChatGPT" },
221
- { pattern: /claude-web/i, type: "claude", name: "Claude" },
222
- { pattern: /perplexitybot/i, type: "perplexity", name: "Perplexity" },
223
- { pattern: /perplexity-user/i, type: "perplexity", name: "Perplexity" },
224
- { pattern: /perplexity-ai/i, type: "perplexity", name: "Perplexity" },
225
- { pattern: /perplexity/i, type: "perplexity", name: "Perplexity" },
226
- // Fallback
227
- { pattern: /bingbot/i, type: "bing", name: "Bing AI" },
228
- { pattern: /anthropic-ai/i, type: "anthropic", name: "Anthropic" }
229
- ];
230
- var CLOUD_PROVIDERS = {
231
- aws: ["54.", "52.", "35.", "18.", "3."],
232
- gcp: ["35.", "34.", "104.", "107.", "108."],
233
- azure: ["13.", "20.", "40.", "52.", "104."]
234
- };
355
+ var rules = loadRulesSync();
235
356
  var EdgeAgentDetector = class {
357
+ rules;
358
+ constructor() {
359
+ this.rules = rules;
360
+ }
236
361
  async analyze(input) {
237
362
  const reasons = [];
238
363
  let detectedAgent;
@@ -251,7 +376,7 @@ var EdgeAgentDetector = class {
251
376
  headers
252
377
  );
253
378
  if (signatureResult.isValid) {
254
- confidence = signatureResult.confidence;
379
+ confidence = signatureResult.confidence * 100;
255
380
  reasons.push(`verified_signature:${signatureResult.agent?.toLowerCase() || "unknown"}`);
256
381
  if (signatureResult.agent) {
257
382
  detectedAgent = {
@@ -264,7 +389,13 @@ var EdgeAgentDetector = class {
264
389
  reasons.push(`keyid:${signatureResult.keyid}`);
265
390
  }
266
391
  } else {
267
- confidence = Math.max(confidence, 0.3);
392
+ console.warn("[EdgeAgentDetector] Signature verification failed:", {
393
+ reason: signatureResult.reason,
394
+ agent: signatureResult.agent,
395
+ hasSignatureAgent: !!headers["signature-agent"] || !!headers["Signature-Agent"],
396
+ signatureAgentValue: headers["signature-agent"] || headers["Signature-Agent"]
397
+ });
398
+ confidence = Math.max(confidence, 30);
268
399
  reasons.push("invalid_signature");
269
400
  if (signatureResult.reason) {
270
401
  reasons.push(`signature_error:${signatureResult.reason}`);
@@ -276,68 +407,133 @@ var EdgeAgentDetector = class {
276
407
  }
277
408
  } catch (error) {
278
409
  console.error("[EdgeAgentDetector] Signature verification error:", error);
279
- confidence = Math.max(confidence, 0.2);
410
+ confidence = Math.max(confidence, 20);
280
411
  reasons.push("signature_verification_error");
281
412
  }
282
413
  }
283
414
  const userAgent = input.userAgent || input.headers?.["user-agent"] || "";
284
415
  if (userAgent) {
285
- for (const { pattern, type, name } of AI_AGENT_PATTERNS) {
286
- if (pattern.test(userAgent)) {
287
- const highConfidenceAgents = [
288
- "chatgpt",
289
- "claude",
290
- "perplexity",
291
- "anthropic"
292
- ];
293
- const patternConfidence = highConfidenceAgents.includes(type) ? 0.85 : 0.5;
294
- confidence = Math.max(confidence, patternConfidence);
295
- reasons.push(`known_pattern:${type}`);
416
+ const userAgentEntries = Object.entries(this.rules.rules.userAgents);
417
+ const genericKeys = ["generic_bot", "dev_tools", "automation_tools"];
418
+ const sortedEntries = userAgentEntries.sort((a, b) => {
419
+ const aIsGeneric = genericKeys.includes(a[0]);
420
+ const bIsGeneric = genericKeys.includes(b[0]);
421
+ if (aIsGeneric && !bIsGeneric) return 1;
422
+ if (!aIsGeneric && bIsGeneric) return -1;
423
+ return 0;
424
+ });
425
+ for (const [agentKey, agentRule] of sortedEntries) {
426
+ const rule = agentRule;
427
+ const matched = rule.patterns.some((pattern) => {
428
+ const regex = new RegExp(pattern, "i");
429
+ return regex.test(userAgent);
430
+ });
431
+ if (matched) {
432
+ const agentType = this.getAgentType(agentKey);
433
+ const agentName = this.getAgentName(agentKey);
434
+ confidence = Math.max(confidence, rule.confidence * 100);
435
+ reasons.push(`known_pattern:${agentType}`);
296
436
  if (!detectedAgent) {
297
- detectedAgent = { type, name };
437
+ detectedAgent = { type: agentType, name: agentName };
298
438
  verificationMethod = "pattern";
299
439
  }
300
440
  break;
301
441
  }
302
442
  }
303
443
  }
304
- const aiHeaders = [
305
- "openai-conversation-id",
306
- "openai-ephemeral-user-id",
307
- "anthropic-client-id",
308
- "x-goog-api-client",
309
- "x-ms-copilot-id"
310
- ];
311
- const foundAiHeaders = aiHeaders.filter(
312
- (header) => normalizedHeaders[header]
444
+ const suspiciousHeaders = this.rules.rules.headers.suspicious;
445
+ const foundAiHeaders = suspiciousHeaders.filter(
446
+ (headerRule) => normalizedHeaders[headerRule.name.toLowerCase()]
313
447
  );
314
448
  if (foundAiHeaders.length > 0) {
315
- confidence = Math.max(confidence, 0.6);
449
+ const maxConfidence = Math.max(...foundAiHeaders.map((h) => h.confidence * 100));
450
+ confidence = Math.max(confidence, maxConfidence);
316
451
  reasons.push(`ai_headers:${foundAiHeaders.length}`);
317
452
  }
318
453
  const ip = input.ip || input.ipAddress;
319
454
  if (ip && !normalizedHeaders["x-forwarded-for"] && !normalizedHeaders["x-real-ip"]) {
320
- for (const [provider, prefixes] of Object.entries(CLOUD_PROVIDERS)) {
321
- if (prefixes.some((prefix) => ip.startsWith(prefix))) {
322
- confidence = Math.max(confidence, 0.4);
455
+ const ipRanges = "providers" in this.rules.rules.ipRanges ? this.rules.rules.ipRanges.providers : this.rules.rules.ipRanges;
456
+ for (const [provider, ipRule] of Object.entries(ipRanges)) {
457
+ if (!ipRule || typeof ipRule !== "object" || !("ranges" in ipRule) || !Array.isArray(ipRule.ranges))
458
+ continue;
459
+ const matched = ipRule.ranges.some((range) => {
460
+ const prefix = range.split("/")[0];
461
+ const prefixParts = prefix.split(".");
462
+ const ipParts = ip.split(".");
463
+ for (let i = 0; i < Math.min(prefixParts.length - 1, 2); i++) {
464
+ if (prefixParts[i] !== ipParts[i] && prefixParts[i] !== "0") {
465
+ return false;
466
+ }
467
+ }
468
+ return true;
469
+ });
470
+ if (matched) {
471
+ const rule = ipRule;
472
+ confidence = Math.max(confidence, rule.confidence * 40);
323
473
  reasons.push(`cloud_provider:${provider}`);
324
474
  break;
325
475
  }
326
476
  }
327
477
  }
328
- if (reasons.length > 2 && confidence < 1) {
329
- confidence = Math.min(confidence * 1.2, 0.95);
478
+ if (reasons.length > 2 && confidence < 100) {
479
+ confidence = Math.min(confidence * 1.2, 95);
330
480
  }
481
+ confidence = Math.min(Math.max(confidence, 0), 100);
331
482
  return {
332
- isAgent: confidence > 0.3,
483
+ isAgent: confidence > 30,
484
+ // Updated to 0-100 scale (was 0.3)
333
485
  confidence,
486
+ detectionClass: confidence > 30 && detectedAgent ? { type: "AiAgent", agentType: detectedAgent.name } : confidence > 30 ? { type: "Unknown" } : { type: "Human" },
487
+ signals: [],
488
+ // Will be populated by enhanced detection engine in future tasks
334
489
  ...detectedAgent && { detectedAgent },
335
490
  reasons,
336
- ...verificationMethod && { verificationMethod },
337
- forgeabilityRisk: verificationMethod === "signature" ? "low" : confidence > 0.8 ? "medium" : "high",
491
+ ...verificationMethod && {
492
+ verificationMethod
493
+ },
494
+ forgeabilityRisk: verificationMethod === "signature" ? "low" : confidence > 80 ? "medium" : "high",
495
+ // Updated to 0-100 scale
338
496
  timestamp: /* @__PURE__ */ new Date()
339
497
  };
340
498
  }
499
+ /**
500
+ * Get agent type from rule key
501
+ */
502
+ getAgentType(agentKey) {
503
+ const typeMap = {
504
+ openai_gptbot: "openai",
505
+ anthropic_claude: "anthropic",
506
+ perplexity_bot: "perplexity",
507
+ google_ai: "google",
508
+ microsoft_ai: "microsoft",
509
+ meta_ai: "meta",
510
+ cohere_bot: "cohere",
511
+ huggingface_bot: "huggingface",
512
+ generic_bot: "generic",
513
+ dev_tools: "dev",
514
+ automation_tools: "automation"
515
+ };
516
+ return typeMap[agentKey] || agentKey;
517
+ }
518
+ /**
519
+ * Get agent name from rule key
520
+ */
521
+ getAgentName(agentKey) {
522
+ const nameMap = {
523
+ openai_gptbot: "ChatGPT/GPTBot",
524
+ anthropic_claude: "Claude",
525
+ perplexity_bot: "Perplexity",
526
+ google_ai: "Google AI",
527
+ microsoft_ai: "Microsoft Copilot",
528
+ meta_ai: "Meta AI",
529
+ cohere_bot: "Cohere",
530
+ huggingface_bot: "HuggingFace",
531
+ generic_bot: "Generic Bot",
532
+ dev_tools: "Development Tool",
533
+ automation_tools: "Automation Tool"
534
+ };
535
+ return nameMap[agentKey] || agentKey;
536
+ }
341
537
  };
342
538
  var EdgeAgentDetectorWrapper = class {
343
539
  detector;
@@ -408,26 +604,31 @@ async function initWasm() {
408
604
  throw new Error(`Failed to fetch WASM: ${response2.status}`);
409
605
  }
410
606
  const streamResponse = response2.clone();
411
- const { instance } = await WebAssembly.instantiateStreaming(
412
- streamResponse,
413
- {
414
- wbg: {
415
- __wbg_log_1d3ae13c3d5e6b8e: (ptr, len) => {
416
- console.log("WASM:", ptr, len);
417
- },
418
- __wbindgen_throw: (ptr, len) => {
419
- throw new Error(`WASM Error at ${ptr}, length ${len}`);
607
+ const { instance } = await WebAssembly.instantiateStreaming(streamResponse, {
608
+ wbg: {
609
+ __wbg_log_1d3ae13c3d5e6b8e: (ptr, len) => {
610
+ if (process.env.NODE_ENV !== "production") {
611
+ console.debug("WASM:", ptr, len);
420
612
  }
613
+ },
614
+ __wbindgen_throw: (ptr, len) => {
615
+ throw new Error(`WASM Error at ${ptr}, length ${len}`);
421
616
  }
422
617
  }
423
- );
618
+ });
424
619
  wasmInstance = instance;
425
620
  wasmExports = instance.exports;
426
- console.log("[AgentShield] \u2705 WASM module initialized with streaming");
621
+ if (process.env.NODE_ENV !== "production") {
622
+ console.debug("[AgentShield] \u2705 WASM module initialized with streaming");
623
+ }
427
624
  return;
428
625
  } catch (streamError) {
429
626
  if (!controller.signal.aborted) {
430
- console.log("[AgentShield] Streaming compilation failed, falling back to standard compilation");
627
+ if (process.env.NODE_ENV !== "production") {
628
+ console.debug(
629
+ "[AgentShield] Streaming compilation failed, falling back to standard compilation"
630
+ );
631
+ }
431
632
  } else {
432
633
  throw streamError;
433
634
  }
@@ -443,7 +644,9 @@ async function initWasm() {
443
644
  const imports = {
444
645
  wbg: {
445
646
  __wbg_log_1d3ae13c3d5e6b8e: (ptr, len) => {
446
- console.log("WASM:", ptr, len);
647
+ if (process.env.NODE_ENV !== "production") {
648
+ console.debug("WASM:", ptr, len);
649
+ }
447
650
  },
448
651
  __wbindgen_throw: (ptr, len) => {
449
652
  throw new Error(`WASM Error at ${ptr}, length ${len}`);
@@ -452,12 +655,20 @@ async function initWasm() {
452
655
  };
453
656
  wasmInstance = await WebAssembly.instantiate(compiledModule, imports);
454
657
  wasmExports = wasmInstance.exports;
455
- console.log("[AgentShield] \u2705 WASM module initialized via fallback");
658
+ if (process.env.NODE_ENV !== "production") {
659
+ console.debug("[AgentShield] \u2705 WASM module initialized via fallback");
660
+ }
456
661
  } catch (fetchError) {
457
- if (fetchError.name === "AbortError") {
458
- console.warn("[AgentShield] WASM fetch timed out after 3 seconds - using pattern detection");
662
+ const error = fetchError;
663
+ if (error.name === "AbortError") {
664
+ console.warn(
665
+ "[AgentShield] WASM fetch timed out after 3 seconds - using pattern detection"
666
+ );
459
667
  } else {
460
- console.warn("[AgentShield] Failed to fetch WASM file:", fetchError.message);
668
+ console.warn(
669
+ "[AgentShield] Failed to fetch WASM file:",
670
+ error.message || "Unknown error"
671
+ );
461
672
  }
462
673
  wasmExports = null;
463
674
  }
@@ -518,30 +729,16 @@ async function isWasmAvailable() {
518
729
  return false;
519
730
  }
520
731
  }
521
-
522
- // src/edge-detector-with-wasm.ts
523
- var AI_AGENT_PATTERNS2 = [
524
- { pattern: /chatgpt-user/i, type: "chatgpt", name: "ChatGPT" },
525
- { pattern: /claude-web/i, type: "claude", name: "Claude" },
526
- { pattern: /perplexitybot/i, type: "perplexity", name: "Perplexity" },
527
- { pattern: /perplexity-user/i, type: "perplexity", name: "Perplexity" },
528
- { pattern: /perplexity-ai/i, type: "perplexity", name: "Perplexity" },
529
- { pattern: /perplexity/i, type: "perplexity", name: "Perplexity" },
530
- { pattern: /bingbot/i, type: "bing", name: "Bing AI" },
531
- { pattern: /anthropic-ai/i, type: "anthropic", name: "Anthropic" }
532
- ];
533
- var CLOUD_PROVIDERS2 = {
534
- aws: ["54.", "52.", "35.", "18.", "3."],
535
- gcp: ["35.", "34.", "104.", "107.", "108."],
536
- azure: ["13.", "20.", "40.", "52.", "104."]
537
- };
732
+ var rules2 = loadRulesSync();
538
733
  var EdgeAgentDetectorWithWasm = class {
539
734
  constructor(enableWasm = true) {
540
735
  this.enableWasm = enableWasm;
736
+ this.rules = rules2;
541
737
  }
542
738
  wasmEnabled = false;
543
739
  initPromise = null;
544
740
  baseUrl = null;
741
+ rules;
545
742
  /**
546
743
  * Set the base URL for WASM loading in Edge Runtime
547
744
  */
@@ -564,11 +761,14 @@ var EdgeAgentDetectorWithWasm = class {
564
761
  this.initPromise = (async () => {
565
762
  try {
566
763
  const wasmAvailable = await isWasmAvailable();
567
- this.wasmEnabled = wasmAvailable;
568
- if (this.wasmEnabled) {
569
- console.log("[AgentShield] WASM detection enabled");
764
+ if (wasmAvailable) {
765
+ if (this.baseUrl) {
766
+ setWasmBaseUrl(this.baseUrl);
767
+ }
768
+ await initWasm();
769
+ this.wasmEnabled = true;
570
770
  } else {
571
- console.log("[AgentShield] WASM not available, using pattern detection");
771
+ this.wasmEnabled = false;
572
772
  }
573
773
  } catch (error) {
574
774
  console.error("[AgentShield] Failed to initialize WASM:", error);
@@ -593,69 +793,84 @@ var EdgeAgentDetectorWithWasm = class {
593
793
  const signaturePresent = !!(normalizedHeaders["signature"] || normalizedHeaders["signature-input"]);
594
794
  const signatureAgent = normalizedHeaders["signature-agent"];
595
795
  if (signatureAgent?.includes("chatgpt.com")) {
596
- confidence = 0.85;
796
+ confidence = 85;
597
797
  reasons.push("signature_agent:chatgpt");
598
798
  detectedAgent = { type: "chatgpt", name: "ChatGPT" };
599
799
  verificationMethod = "signature";
600
800
  } else if (signaturePresent) {
601
- confidence = Math.max(confidence, 0.4);
801
+ confidence = Math.max(confidence, 40);
602
802
  reasons.push("signature_present");
603
803
  }
604
804
  const userAgent = input.userAgent || input.headers?.["user-agent"] || "";
605
805
  if (userAgent) {
606
- for (const { pattern, type, name } of AI_AGENT_PATTERNS2) {
607
- if (pattern.test(userAgent)) {
608
- const highConfidenceAgents = [
609
- "chatgpt",
610
- "claude",
611
- "perplexity",
612
- "anthropic"
613
- ];
614
- const patternConfidence = highConfidenceAgents.includes(type) ? 0.85 : 0.5;
615
- confidence = Math.max(confidence, patternConfidence);
616
- reasons.push(`known_pattern:${type}`);
806
+ for (const [agentKey, agentRule] of Object.entries(this.rules.rules.userAgents)) {
807
+ const matched = agentRule.patterns.some((pattern) => {
808
+ const regex = new RegExp(pattern, "i");
809
+ return regex.test(userAgent);
810
+ });
811
+ if (matched) {
812
+ const agentType = this.getAgentType(agentKey);
813
+ const agentName = this.getAgentName(agentKey);
814
+ confidence = Math.max(confidence, Math.round(agentRule.confidence * 0.85 * 100));
815
+ reasons.push(`known_pattern:${agentType}`);
617
816
  if (!detectedAgent) {
618
- detectedAgent = { type, name };
817
+ detectedAgent = { type: agentType, name: agentName };
619
818
  verificationMethod = "pattern";
620
819
  }
621
820
  break;
622
821
  }
623
822
  }
624
823
  }
625
- const aiHeaders = [
626
- "openai-conversation-id",
627
- "openai-ephemeral-user-id",
628
- "anthropic-client-id",
629
- "x-goog-api-client",
630
- "x-ms-copilot-id"
631
- ];
632
- const foundAiHeaders = aiHeaders.filter(
633
- (header) => normalizedHeaders[header]
824
+ const suspiciousHeaders = this.rules.rules.headers.suspicious;
825
+ const foundAiHeaders = suspiciousHeaders.filter(
826
+ (headerRule) => normalizedHeaders[headerRule.name.toLowerCase()]
634
827
  );
635
828
  if (foundAiHeaders.length > 0) {
636
- confidence = Math.max(confidence, 0.6);
829
+ const maxConfidence = Math.max(...foundAiHeaders.map((h) => h.confidence));
830
+ confidence = Math.max(confidence, maxConfidence);
637
831
  reasons.push(`ai_headers:${foundAiHeaders.length}`);
638
832
  }
639
833
  const ip = input.ip || input.ipAddress;
640
834
  if (ip && !normalizedHeaders["x-forwarded-for"] && !normalizedHeaders["x-real-ip"]) {
641
- for (const [provider, prefixes] of Object.entries(CLOUD_PROVIDERS2)) {
642
- if (prefixes.some((prefix) => ip.startsWith(prefix))) {
643
- confidence = Math.max(confidence, 0.4);
835
+ const ipRanges = "providers" in this.rules.rules.ipRanges ? this.rules.rules.ipRanges.providers : this.rules.rules.ipRanges;
836
+ for (const [provider, ipRule] of Object.entries(ipRanges)) {
837
+ if (!ipRule || typeof ipRule !== "object" || !("ranges" in ipRule) || !Array.isArray(ipRule.ranges))
838
+ continue;
839
+ const matched = ipRule.ranges.some((range) => {
840
+ const prefix = range.split("/")[0];
841
+ const prefixParts = prefix.split(".");
842
+ const ipParts = ip.split(".");
843
+ for (let i = 0; i < Math.min(prefixParts.length - 1, 2); i++) {
844
+ if (prefixParts[i] !== ipParts[i] && prefixParts[i] !== "0") {
845
+ return false;
846
+ }
847
+ }
848
+ return true;
849
+ });
850
+ if (matched) {
851
+ confidence = Math.max(confidence, Math.round(ipRule.confidence * 0.4 * 100));
644
852
  reasons.push(`cloud_provider:${provider}`);
645
853
  break;
646
854
  }
647
855
  }
648
856
  }
649
857
  if (reasons.length > 2) {
650
- confidence = Math.min(confidence * 1.2, 0.95);
858
+ confidence = Math.min(Math.round(confidence * 1.2), 95);
651
859
  }
860
+ confidence = Math.min(Math.max(confidence, 0), 100);
652
861
  return {
653
- isAgent: confidence > 0.3,
862
+ isAgent: confidence > 30,
863
+ // 30% threshold
654
864
  confidence,
865
+ detectionClass: confidence > 30 && detectedAgent ? { type: "AiAgent", agentType: detectedAgent.name } : confidence > 30 ? { type: "Unknown" } : { type: "Human" },
866
+ signals: [],
867
+ // Will be populated by enhanced detection engine in future tasks
655
868
  ...detectedAgent && { detectedAgent },
656
869
  reasons,
657
- ...verificationMethod && { verificationMethod },
658
- forgeabilityRisk: confidence > 0.8 ? "medium" : "high",
870
+ ...verificationMethod && {
871
+ verificationMethod: mapVerificationMethod(verificationMethod)
872
+ },
873
+ forgeabilityRisk: confidence > 80 ? "medium" : "high",
659
874
  timestamp: /* @__PURE__ */ new Date()
660
875
  };
661
876
  }
@@ -672,15 +887,17 @@ var EdgeAgentDetectorWithWasm = class {
672
887
  input.ip || input.ipAddress
673
888
  );
674
889
  if (wasmResult) {
675
- console.log("[AgentShield] Using WASM detection result");
676
890
  const detectedAgent = wasmResult.agent ? this.mapAgentName(wasmResult.agent) : void 0;
677
891
  return {
678
892
  isAgent: wasmResult.isAgent,
679
893
  confidence: wasmResult.confidence,
894
+ detectionClass: wasmResult.isAgent && detectedAgent ? { type: "AiAgent", agentType: detectedAgent.name } : wasmResult.isAgent ? { type: "Unknown" } : { type: "Human" },
895
+ signals: [],
896
+ // Will be populated by enhanced detection engine in future tasks
680
897
  ...detectedAgent && { detectedAgent },
681
898
  reasons: [`wasm:${wasmResult.verificationMethod}`],
682
- verificationMethod: wasmResult.verificationMethod,
683
- forgeabilityRisk: wasmResult.verificationMethod === "signature" ? "low" : wasmResult.confidence > 0.9 ? "medium" : "high",
899
+ verificationMethod: mapVerificationMethod(wasmResult.verificationMethod),
900
+ forgeabilityRisk: wasmResult.verificationMethod === "signature" ? "low" : wasmResult.confidence > 90 ? "medium" : "high",
684
901
  timestamp: /* @__PURE__ */ new Date()
685
902
  };
686
903
  }
@@ -689,15 +906,50 @@ var EdgeAgentDetectorWithWasm = class {
689
906
  }
690
907
  }
691
908
  const patternResult = await this.patternDetection(input);
692
- if (this.wasmEnabled && patternResult.confidence >= 0.85) {
693
- patternResult.confidence = Math.min(0.95, patternResult.confidence + 0.1);
909
+ if (this.wasmEnabled && patternResult.confidence >= 85) {
910
+ patternResult.confidence = Math.min(95, patternResult.confidence + 10);
694
911
  patternResult.reasons.push("wasm_enhanced");
695
- if (!patternResult.verificationMethod) {
696
- patternResult.verificationMethod = "wasm-enhanced";
697
- }
698
912
  }
699
913
  return patternResult;
700
914
  }
915
+ /**
916
+ * Get agent type from rule key
917
+ */
918
+ getAgentType(agentKey) {
919
+ const typeMap = {
920
+ openai_gptbot: "openai",
921
+ anthropic_claude: "anthropic",
922
+ perplexity_bot: "perplexity",
923
+ google_ai: "google",
924
+ microsoft_ai: "microsoft",
925
+ meta_ai: "meta",
926
+ cohere_bot: "cohere",
927
+ huggingface_bot: "huggingface",
928
+ generic_bot: "generic",
929
+ dev_tools: "dev",
930
+ automation_tools: "automation"
931
+ };
932
+ return typeMap[agentKey] || agentKey;
933
+ }
934
+ /**
935
+ * Get agent name from rule key
936
+ */
937
+ getAgentName(agentKey) {
938
+ const nameMap = {
939
+ openai_gptbot: "ChatGPT/GPTBot",
940
+ anthropic_claude: "Claude",
941
+ perplexity_bot: "Perplexity",
942
+ google_ai: "Google AI",
943
+ microsoft_ai: "Microsoft Copilot",
944
+ meta_ai: "Meta AI",
945
+ cohere_bot: "Cohere",
946
+ huggingface_bot: "HuggingFace",
947
+ generic_bot: "Generic Bot",
948
+ dev_tools: "Development Tool",
949
+ automation_tools: "Automation Tool"
950
+ };
951
+ return nameMap[agentKey] || agentKey;
952
+ }
701
953
  /**
702
954
  * Map agent names from WASM to consistent format
703
955
  */
@@ -878,7 +1130,9 @@ function createAgentShieldMiddleware(config = {}) {
878
1130
  }) : null;
879
1131
  if (config.events) {
880
1132
  Object.entries(config.events).forEach(([event, handler]) => {
881
- detector.on(event, handler);
1133
+ if (handler) {
1134
+ detector.on(event, handler);
1135
+ }
882
1136
  });
883
1137
  }
884
1138
  const {
@@ -910,19 +1164,22 @@ function createAgentShieldMiddleware(config = {}) {
910
1164
  const response2 = NextResponse.next();
911
1165
  response2.headers.set("x-agentshield-detected", "true");
912
1166
  response2.headers.set("x-agentshield-agent", existingSession.agent);
913
- response2.headers.set(
914
- "x-agentshield-confidence",
915
- existingSession.confidence.toString()
916
- );
1167
+ response2.headers.set("x-agentshield-confidence", existingSession.confidence.toString());
917
1168
  response2.headers.set("x-agentshield-session", "continued");
918
1169
  response2.headers.set("x-agentshield-session-id", existingSession.id);
919
1170
  request.agentShield = {
920
1171
  result: {
921
1172
  isAgent: true,
922
1173
  confidence: existingSession.confidence,
923
- detectedAgent: { name: existingSession.agent },
1174
+ detectionClass: { type: "AiAgent" },
1175
+ detectedAgent: {
1176
+ type: "ai_agent",
1177
+ name: existingSession.agent
1178
+ },
924
1179
  timestamp: /* @__PURE__ */ new Date(),
925
- verificationMethod: "session"
1180
+ verificationMethod: "behavioral",
1181
+ reasons: ["Session continued"],
1182
+ signals: []
926
1183
  },
927
1184
  session: existingSession,
928
1185
  skipped: false
@@ -952,7 +1209,7 @@ function createAgentShieldMiddleware(config = {}) {
952
1209
  timestamp: /* @__PURE__ */ new Date()
953
1210
  };
954
1211
  const result = await detector.analyze(context);
955
- if (result.isAgent && result.confidence >= (config.confidenceThreshold ?? 0.7)) {
1212
+ if (result.isAgent && result.confidence >= (config.confidenceThreshold ?? 70)) {
956
1213
  if (onDetection) {
957
1214
  const customResponse = await onDetection(request, result);
958
1215
  if (customResponse) {
@@ -971,11 +1228,9 @@ function createAgentShieldMiddleware(config = {}) {
971
1228
  { status: blockedResponse.status }
972
1229
  );
973
1230
  if (blockedResponse.headers) {
974
- Object.entries(blockedResponse.headers).forEach(
975
- ([key, value]) => {
976
- response2.headers.set(key, value);
977
- }
978
- );
1231
+ Object.entries(blockedResponse.headers).forEach(([key, value]) => {
1232
+ response2.headers.set(key, value);
1233
+ });
979
1234
  }
980
1235
  detector.emit("agent.blocked", result, context);
981
1236
  return response2;
@@ -985,17 +1240,19 @@ function createAgentShieldMiddleware(config = {}) {
985
1240
  case "rewrite":
986
1241
  return NextResponse.rewrite(new URL(rewriteUrl, request.url));
987
1242
  case "log":
988
- console.warn("AgentShield: Agent detected", {
989
- ipAddress: context.ipAddress,
990
- userAgent: context.userAgent,
991
- confidence: result.confidence,
992
- reasons: result.reasons,
993
- pathname: request.nextUrl.pathname
994
- });
1243
+ if (process.env.NODE_ENV !== "production") {
1244
+ console.debug("AgentShield: Agent detected", {
1245
+ ipAddress: context.ipAddress,
1246
+ userAgent: context.userAgent,
1247
+ confidence: result.confidence,
1248
+ reasons: result.reasons,
1249
+ pathname: request.nextUrl.pathname
1250
+ });
1251
+ }
995
1252
  break;
996
1253
  case "allow":
997
1254
  default:
998
- if (result.isAgent && result.confidence >= (config.confidenceThreshold ?? 0.7)) {
1255
+ if (result.isAgent && result.confidence >= (config.confidenceThreshold ?? 70)) {
999
1256
  detector.emit("agent.allowed", result, context);
1000
1257
  }
1001
1258
  break;
@@ -1007,14 +1264,11 @@ function createAgentShieldMiddleware(config = {}) {
1007
1264
  };
1008
1265
  let response = NextResponse.next();
1009
1266
  response.headers.set("x-agentshield-detected", result.isAgent.toString());
1010
- response.headers.set(
1011
- "x-agentshield-confidence",
1012
- result.confidence.toString()
1013
- );
1267
+ response.headers.set("x-agentshield-confidence", result.confidence.toString());
1014
1268
  if (result.detectedAgent?.name) {
1015
1269
  response.headers.set("x-agentshield-agent", result.detectedAgent.name);
1016
1270
  }
1017
- if (sessionTracker && result.isAgent && result.confidence >= (config.confidenceThreshold ?? 0.7)) {
1271
+ if (sessionTracker && result.isAgent && result.confidence >= (config.confidenceThreshold ?? 70)) {
1018
1272
  response = await sessionTracker.track(request, response, result);
1019
1273
  response.headers.set("x-agentshield-session", "new");
1020
1274
  detector.emit("agent.session.started", result, context);