@kya-os/agentshield-nextjs 0.1.41 → 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 +821 -233
  51. package/dist/index.js.map +1 -1
  52. package/dist/index.mjs +797 -234
  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 +27 -6
  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,15 +1,127 @@
1
+ import * as ed25519 from '@noble/ed25519';
2
+ import { sha512 } from '@noble/hashes/sha2';
3
+
1
4
  // src/signature-verifier.ts
5
+ ed25519.etc.sha512Sync = (...m) => sha512(ed25519.etc.concatBytes(...m));
2
6
  var KNOWN_KEYS = {
3
7
  chatgpt: [
4
8
  {
5
9
  kid: "otMqcjr17mGyruktGvJU8oojQTSMHlVm7uO-lrcqbdg",
6
10
  // ChatGPT's current Ed25519 public key (base64)
11
+ // Source: https://chatgpt.com/.well-known/http-message-signatures-directory
7
12
  publicKey: "7F_3jDlxaquwh291MiACkcS3Opq88NksyHiakzS-Y1g",
8
- validFrom: (/* @__PURE__ */ new Date("2025-01-01")).getTime() / 1e3,
9
- validUntil: (/* @__PURE__ */ new Date("2025-04-11")).getTime() / 1e3
13
+ validFrom: 1735689600,
14
+ // Jan 1, 2025 (from OpenAI's nbf)
15
+ // Extended expiration as fallback safety - API fetch should provide fresh keys
16
+ // Check OpenAI's well-known endpoint for actual expiration dates
17
+ validUntil: 1799625600
18
+ // Jan 1, 2027 (extended for fallback safety)
10
19
  }
11
20
  ]
12
21
  };
22
+ var keyCache = /* @__PURE__ */ new Map();
23
+ var CACHE_TTL_MS = 5 * 60 * 1e3;
24
+ var CACHE_MAX_SIZE = 100;
25
+ function getApiBaseUrl() {
26
+ if (typeof window !== "undefined") {
27
+ return "/api/internal";
28
+ }
29
+ const baseUrl = 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);
30
+ if (baseUrl) {
31
+ return baseUrl.replace(/\/$/, "") + "/api/internal";
32
+ }
33
+ if (process.env.NODE_ENV !== "production") {
34
+ console.warn(
35
+ "[Signature] No base URL configured for server-side fetch. Using localhost fallback."
36
+ );
37
+ return "http://localhost:3000/api/internal";
38
+ }
39
+ console.error(
40
+ "[Signature] CRITICAL: No base URL configured for server-side fetch in production!"
41
+ );
42
+ return "/api/internal";
43
+ }
44
+ function cleanupExpiredCache() {
45
+ const now = Date.now();
46
+ const entriesToDelete = [];
47
+ for (const [agent, cached] of keyCache.entries()) {
48
+ if (now - cached.cachedAt > CACHE_TTL_MS) {
49
+ entriesToDelete.push(agent);
50
+ }
51
+ }
52
+ for (const agent of entriesToDelete) {
53
+ keyCache.delete(agent);
54
+ }
55
+ if (keyCache.size > CACHE_MAX_SIZE) {
56
+ const entries = Array.from(keyCache.entries()).map(([agent, cached]) => ({
57
+ agent,
58
+ cachedAt: cached.cachedAt
59
+ }));
60
+ entries.sort((a, b) => a.cachedAt - b.cachedAt);
61
+ const toRemove = entries.slice(0, keyCache.size - CACHE_MAX_SIZE);
62
+ for (const entry of toRemove) {
63
+ keyCache.delete(entry.agent);
64
+ }
65
+ }
66
+ }
67
+ async function fetchKeysFromApi(agent) {
68
+ if (keyCache.size > CACHE_MAX_SIZE) {
69
+ cleanupExpiredCache();
70
+ }
71
+ const cached = keyCache.get(agent);
72
+ if (cached && Date.now() - cached.cachedAt < CACHE_TTL_MS) {
73
+ return cached.keys;
74
+ }
75
+ if (typeof fetch === "undefined") {
76
+ console.warn("[Signature] fetch() not available in this environment");
77
+ return null;
78
+ }
79
+ try {
80
+ const apiBaseUrl = getApiBaseUrl();
81
+ const url = `${apiBaseUrl}/signature-keys?agent=${encodeURIComponent(agent)}`;
82
+ const response = await fetch(url, {
83
+ method: "GET",
84
+ headers: {
85
+ "Content-Type": "application/json"
86
+ },
87
+ // 5 second timeout
88
+ signal: AbortSignal.timeout(5e3)
89
+ });
90
+ if (!response.ok) {
91
+ console.warn(`[Signature] Failed to fetch keys from API: ${response.status}`);
92
+ return null;
93
+ }
94
+ const data = await response.json();
95
+ if (!data.keys || !Array.isArray(data.keys) || data.keys.length === 0) {
96
+ console.warn(`[Signature] No keys returned from API for agent: ${agent}`);
97
+ return null;
98
+ }
99
+ keyCache.set(agent, {
100
+ keys: data.keys,
101
+ cachedAt: Date.now()
102
+ });
103
+ return data.keys;
104
+ } catch (error) {
105
+ console.warn("[Signature] Error fetching keys from API, using fallback", {
106
+ error: error instanceof Error ? error.message : "Unknown error",
107
+ agent
108
+ });
109
+ return null;
110
+ }
111
+ }
112
+ function isValidAgent(agent) {
113
+ return agent in KNOWN_KEYS;
114
+ }
115
+ async function getKeysForAgent(agent) {
116
+ const apiKeys = await fetchKeysFromApi(agent);
117
+ if (apiKeys && apiKeys.length > 0) {
118
+ return apiKeys;
119
+ }
120
+ if (isValidAgent(agent)) {
121
+ return KNOWN_KEYS[agent];
122
+ }
123
+ return [];
124
+ }
13
125
  function parseSignatureInput(signatureInput) {
14
126
  try {
15
127
  const match = signatureInput.match(/sig1=\((.*?)\);(.+)/);
@@ -45,21 +157,29 @@ function buildSignatureBase(method, path, headers, signedHeaders) {
45
157
  case "@authority":
46
158
  value = headers["host"] || headers["Host"] || "";
47
159
  break;
48
- default:
49
- const key = Object.keys(headers).find(
50
- (k) => k.toLowerCase() === headerName.toLowerCase()
51
- );
160
+ default: {
161
+ const key = Object.keys(headers).find((k) => k.toLowerCase() === headerName.toLowerCase());
52
162
  value = key ? headers[key] || "" : "";
53
163
  break;
164
+ }
54
165
  }
55
166
  components.push(`"${headerName}": ${value}`);
56
167
  }
57
168
  return components.join("\n");
58
169
  }
170
+ function base64ToBytes(base64) {
171
+ let standardBase64 = base64.replace(/-/g, "+").replace(/_/g, "/");
172
+ const padding = standardBase64.length % 4;
173
+ if (padding) {
174
+ standardBase64 += "=".repeat(4 - padding);
175
+ }
176
+ const binaryString = atob(standardBase64);
177
+ return Uint8Array.from(binaryString, (c) => c.charCodeAt(0));
178
+ }
59
179
  async function verifyEd25519Signature(publicKeyBase64, signatureBase64, message) {
60
180
  try {
61
- const publicKeyBytes = Uint8Array.from(atob(publicKeyBase64), (c) => c.charCodeAt(0));
62
- const signatureBytes = Uint8Array.from(atob(signatureBase64), (c) => c.charCodeAt(0));
181
+ const publicKeyBytes = base64ToBytes(publicKeyBase64);
182
+ const signatureBytes = base64ToBytes(signatureBase64);
63
183
  const messageBytes = new TextEncoder().encode(message);
64
184
  if (publicKeyBytes.length !== 32) {
65
185
  console.error("[Signature] Invalid public key length:", publicKeyBytes.length);
@@ -69,34 +189,36 @@ async function verifyEd25519Signature(publicKeyBase64, signatureBase64, message)
69
189
  console.error("[Signature] Invalid signature length:", signatureBytes.length);
70
190
  return false;
71
191
  }
72
- const publicKey = await crypto.subtle.importKey(
73
- "raw",
74
- publicKeyBytes,
75
- {
76
- name: "Ed25519",
77
- namedCurve: "Ed25519"
78
- },
79
- false,
80
- ["verify"]
81
- );
82
- const isValid = await crypto.subtle.verify(
83
- "Ed25519",
84
- publicKey,
85
- signatureBytes,
86
- messageBytes
87
- );
88
- return isValid;
89
- } catch (error) {
90
- console.error("[Signature] Ed25519 verification failed:", error);
91
- if (typeof window === "undefined") {
92
- try {
93
- console.warn("[Signature] Ed25519 not supported in this environment");
94
- return false;
95
- } catch {
96
- return false;
97
- }
192
+ return ed25519.verify(signatureBytes, messageBytes, publicKeyBytes);
193
+ } catch (nobleError) {
194
+ console.warn("[Signature] @noble/ed25519 failed, trying Web Crypto fallback:", nobleError);
195
+ try {
196
+ const publicKeyBytes = base64ToBytes(publicKeyBase64);
197
+ const signatureBytes = base64ToBytes(signatureBase64);
198
+ const messageBytes = new TextEncoder().encode(message);
199
+ const publicKey = await crypto.subtle.importKey(
200
+ "raw",
201
+ publicKeyBytes.buffer,
202
+ {
203
+ name: "Ed25519",
204
+ namedCurve: "Ed25519"
205
+ },
206
+ false,
207
+ ["verify"]
208
+ );
209
+ return await crypto.subtle.verify(
210
+ "Ed25519",
211
+ publicKey,
212
+ signatureBytes.buffer,
213
+ messageBytes
214
+ );
215
+ } catch (cryptoError) {
216
+ console.error("[Signature] Both @noble/ed25519 and Web Crypto failed:", {
217
+ nobleError: nobleError instanceof Error ? nobleError.message : "Unknown",
218
+ cryptoError: cryptoError instanceof Error ? cryptoError.message : "Unknown"
219
+ });
220
+ return false;
98
221
  }
99
- return false;
100
222
  }
101
223
  }
102
224
  async function verifyAgentSignature(method, path, headers) {
@@ -141,12 +263,12 @@ async function verifyAgentSignature(method, path, headers) {
141
263
  }
142
264
  }
143
265
  let agent;
144
- let knownKeys;
266
+ let agentKey;
145
267
  if (signatureAgent === '"https://chatgpt.com"' || signatureAgent?.includes("chatgpt.com")) {
146
268
  agent = "ChatGPT";
147
- knownKeys = KNOWN_KEYS.chatgpt;
269
+ agentKey = "chatgpt";
148
270
  }
149
- if (!agent || !knownKeys) {
271
+ if (!agent || !agentKey) {
150
272
  return {
151
273
  isValid: false,
152
274
  confidence: 0,
@@ -154,6 +276,15 @@ async function verifyAgentSignature(method, path, headers) {
154
276
  verificationMethod: "none"
155
277
  };
156
278
  }
279
+ const knownKeys = await getKeysForAgent(agentKey);
280
+ if (knownKeys.length === 0) {
281
+ return {
282
+ isValid: false,
283
+ confidence: 0,
284
+ reason: "No keys available for agent",
285
+ verificationMethod: "none"
286
+ };
287
+ }
157
288
  const key = knownKeys.find((k) => k.kid === parsed.keyid);
158
289
  if (!key) {
159
290
  return {
@@ -180,11 +311,7 @@ async function verifyAgentSignature(method, path, headers) {
180
311
  if (signatureValue.endsWith(":")) {
181
312
  signatureValue = signatureValue.slice(0, -1);
182
313
  }
183
- const isValid = await verifyEd25519Signature(
184
- key.publicKey,
185
- signatureValue,
186
- signatureBase
187
- );
314
+ const isValid = await verifyEd25519Signature(key.publicKey, signatureValue, signatureBase);
188
315
  if (isValid) {
189
316
  return {
190
317
  isValid: true,
@@ -208,7 +335,20 @@ function hasSignatureHeaders(headers) {
208
335
  }
209
336
  function isChatGPTSignature(headers) {
210
337
  const signatureAgent = headers["signature-agent"] || headers["Signature-Agent"];
211
- return signatureAgent === '"https://chatgpt.com"' || (signatureAgent?.includes("chatgpt.com") || false);
338
+ if (!signatureAgent) {
339
+ return false;
340
+ }
341
+ const agentUrlStr = signatureAgent.replace(/^"+|"+$/g, "");
342
+ if (agentUrlStr === "https://chatgpt.com") {
343
+ return true;
344
+ }
345
+ try {
346
+ const agentUrl = new URL(agentUrlStr);
347
+ const allowedHosts = ["chatgpt.com", "www.chatgpt.com"];
348
+ return allowedHosts.includes(agentUrl.host);
349
+ } catch {
350
+ return false;
351
+ }
212
352
  }
213
353
 
214
354
  export { hasSignatureHeaders, isChatGPTSignature, verifyAgentSignature };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/signature-verifier.ts"],"names":["now"],"mappings":"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","file":"signature-verifier.mjs","sourcesContent":["/**\n * Ed25519 Signature Verification for HTTP Message Signatures\n * Implements proper cryptographic verification for ChatGPT and other agents\n * \n * Based on RFC 9421 (HTTP Message Signatures) and ChatGPT's implementation\n * Reference: https://help.openai.com/en/articles/9785974-chatgpt-user-allowlisting\n */\n\n/**\n * Known public keys for AI agents\n */\nconst KNOWN_KEYS = {\n chatgpt: [\n {\n kid: 'otMqcjr17mGyruktGvJU8oojQTSMHlVm7uO-lrcqbdg',\n // ChatGPT's current Ed25519 public key (base64)\n publicKey: '7F_3jDlxaquwh291MiACkcS3Opq88NksyHiakzS-Y1g',\n validFrom: new Date('2025-01-01').getTime() / 1000,\n validUntil: new Date('2025-04-11').getTime() / 1000,\n },\n ],\n};\n\n/**\n * Parse the Signature-Input header according to RFC 9421\n */\nfunction parseSignatureInput(signatureInput: string): {\n keyid: string;\n created?: number | undefined;\n expires?: number | undefined;\n signedHeaders: string[];\n} | null {\n try {\n // Example: sig1=(\"@method\" \"@path\" \"@authority\" \"date\");keyid=\"...\";created=1234567890\n const match = signatureInput.match(/sig1=\\((.*?)\\);(.+)/);\n if (!match) return null;\n\n const [, headersList, params] = match;\n \n // Parse signed headers\n const signedHeaders = headersList\n ? headersList\n .split(' ')\n .map(h => h.replace(/\"/g, '').trim())\n .filter(h => h.length > 0)\n : [];\n\n // Parse parameters\n const keyidMatch = params ? params.match(/keyid=\"([^\"]+)\"/) : null;\n const createdMatch = params ? params.match(/created=(\\d+)/) : null;\n const expiresMatch = params ? params.match(/expires=(\\d+)/) : null;\n\n if (!keyidMatch || !keyidMatch[1]) return null;\n\n return {\n keyid: keyidMatch[1],\n created: createdMatch && createdMatch[1] ? parseInt(createdMatch[1]) : undefined,\n expires: expiresMatch && expiresMatch[1] ? parseInt(expiresMatch[1]) : undefined,\n signedHeaders,\n };\n } catch (error) {\n console.error('[Signature] Failed to parse Signature-Input:', error);\n return null;\n }\n}\n\n/**\n * Build the signature base string according to RFC 9421\n * This is what gets signed\n */\nfunction buildSignatureBase(\n method: string,\n path: string,\n headers: Record<string, string>,\n signedHeaders: string[]\n): string {\n const components: string[] = [];\n \n for (const headerName of signedHeaders) {\n let value: string;\n \n switch (headerName) {\n case '@method':\n value = method.toUpperCase();\n break;\n case '@path':\n value = path;\n break;\n case '@authority':\n // Get from Host header or URL\n value = headers['host'] || headers['Host'] || '';\n break;\n default:\n // Regular headers (case-insensitive lookup)\n const key = Object.keys(headers).find(\n k => k.toLowerCase() === headerName.toLowerCase()\n );\n value = key ? headers[key] || '' : '';\n break;\n }\n \n // Format according to RFC 9421\n components.push(`\"${headerName}\": ${value}`);\n }\n \n return components.join('\\n');\n}\n\n/**\n * Verify Ed25519 signature using Web Crypto API\n */\nasync function verifyEd25519Signature(\n publicKeyBase64: string,\n signatureBase64: string,\n message: string\n): Promise<boolean> {\n try {\n // Decode base64 to Uint8Array\n const publicKeyBytes = Uint8Array.from(atob(publicKeyBase64), c => c.charCodeAt(0));\n const signatureBytes = Uint8Array.from(atob(signatureBase64), c => c.charCodeAt(0));\n const messageBytes = new TextEncoder().encode(message);\n \n // Check key and signature lengths\n if (publicKeyBytes.length !== 32) {\n console.error('[Signature] Invalid public key length:', publicKeyBytes.length);\n return false;\n }\n if (signatureBytes.length !== 64) {\n console.error('[Signature] Invalid signature length:', signatureBytes.length);\n return false;\n }\n \n // Import the public key\n const publicKey = await crypto.subtle.importKey(\n 'raw',\n publicKeyBytes,\n {\n name: 'Ed25519',\n namedCurve: 'Ed25519',\n },\n false,\n ['verify']\n );\n \n // Verify the signature\n const isValid = await crypto.subtle.verify(\n 'Ed25519',\n publicKey,\n signatureBytes,\n messageBytes\n );\n \n return isValid;\n } catch (error) {\n console.error('[Signature] Ed25519 verification failed:', error);\n \n // Fallback: Try with @noble/ed25519 if available (for environments without Ed25519 support)\n if (typeof window === 'undefined') {\n try {\n // In Node.js/Edge Runtime, we might need to use a polyfill\n // For now, we'll return false if Web Crypto doesn't support Ed25519\n console.warn('[Signature] Ed25519 not supported in this environment');\n return false;\n } catch {\n return false;\n }\n }\n \n return false;\n }\n}\n\n/**\n * Signature verification result\n */\nexport interface SignatureVerificationResult {\n isValid: boolean;\n agent?: string;\n keyid?: string;\n confidence: number;\n reason?: string;\n verificationMethod: 'signature' | 'none';\n}\n\n/**\n * Verify HTTP Message Signature for AI agents\n */\nexport async function verifyAgentSignature(\n method: string,\n path: string,\n headers: Record<string, string>\n): Promise<SignatureVerificationResult> {\n // Check for signature headers\n const signature = headers['signature'] || headers['Signature'];\n const signatureInput = headers['signature-input'] || headers['Signature-Input'];\n const signatureAgent = headers['signature-agent'] || headers['Signature-Agent'];\n \n // No signature present\n if (!signature || !signatureInput) {\n return {\n isValid: false,\n confidence: 0,\n reason: 'No signature headers present',\n verificationMethod: 'none',\n };\n }\n \n // Parse Signature-Input header\n const parsed = parseSignatureInput(signatureInput);\n if (!parsed) {\n return {\n isValid: false,\n confidence: 0,\n reason: 'Invalid Signature-Input header',\n verificationMethod: 'none',\n };\n }\n \n // Check timestamp if present\n if (parsed.created) {\n const now = Math.floor(Date.now() / 1000);\n const age = now - parsed.created;\n \n // Reject signatures older than 5 minutes\n if (age > 300) {\n return {\n isValid: false,\n confidence: 0,\n reason: 'Signature expired (older than 5 minutes)',\n verificationMethod: 'none',\n };\n }\n \n // Reject signatures from the future (clock skew tolerance: 30 seconds)\n if (age < -30) {\n return {\n isValid: false,\n confidence: 0,\n reason: 'Signature timestamp is in the future',\n verificationMethod: 'none',\n };\n }\n }\n \n // Determine which agent based on signature-agent header\n let agent: string | undefined;\n let knownKeys: typeof KNOWN_KEYS.chatgpt | undefined;\n \n if (signatureAgent === '\"https://chatgpt.com\"' || signatureAgent?.includes('chatgpt.com')) {\n agent = 'ChatGPT';\n knownKeys = KNOWN_KEYS.chatgpt;\n }\n // Add other agents here as needed\n \n if (!agent || !knownKeys) {\n return {\n isValid: false,\n confidence: 0,\n reason: 'Unknown signature agent',\n verificationMethod: 'none',\n };\n }\n \n // Find the key by ID\n const key = knownKeys.find(k => k.kid === parsed.keyid);\n if (!key) {\n return {\n isValid: false,\n confidence: 0,\n reason: `Unknown key ID: ${parsed.keyid}`,\n verificationMethod: 'none',\n };\n }\n \n // Check key validity period\n const now = Math.floor(Date.now() / 1000);\n if (now < key.validFrom || now > key.validUntil) {\n return {\n isValid: false,\n confidence: 0,\n reason: 'Key is not valid at current time',\n verificationMethod: 'none',\n };\n }\n \n // Build the signature base string\n const signatureBase = buildSignatureBase(method, path, headers, parsed.signedHeaders);\n \n // Extract the actual signature value (remove \"sig1=:\" prefix and \"::\" suffix if present)\n let signatureValue = signature;\n if (signatureValue.startsWith('sig1=:')) {\n signatureValue = signatureValue.substring(6);\n }\n if (signatureValue.endsWith(':')) {\n signatureValue = signatureValue.slice(0, -1);\n }\n \n // Verify the signature\n const isValid = await verifyEd25519Signature(\n key.publicKey,\n signatureValue,\n signatureBase\n );\n \n if (isValid) {\n return {\n isValid: true,\n agent,\n keyid: parsed.keyid,\n confidence: 1.0, // 100% confidence for valid signature\n verificationMethod: 'signature',\n };\n } else {\n return {\n isValid: false,\n confidence: 0,\n reason: 'Signature verification failed',\n verificationMethod: 'none',\n };\n }\n}\n\n/**\n * Quick check if signature headers are present (for performance)\n */\nexport function hasSignatureHeaders(headers: Record<string, string>): boolean {\n return !!(\n (headers['signature'] || headers['Signature']) &&\n (headers['signature-input'] || headers['Signature-Input'])\n );\n}\n\n/**\n * Check if this is a ChatGPT signature based on headers\n */\nexport function isChatGPTSignature(headers: Record<string, string>): boolean {\n const signatureAgent = headers['signature-agent'] || headers['Signature-Agent'];\n return signatureAgent === '\"https://chatgpt.com\"' || (signatureAgent?.includes('chatgpt.com') || false);\n}"]}
1
+ {"version":3,"sources":["../src/signature-verifier.ts"],"names":["now"],"mappings":"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","file":"signature-verifier.mjs","sourcesContent":["/**\n * Ed25519 Signature Verification for HTTP Message Signatures\n * Implements proper cryptographic verification for ChatGPT and other agents\n *\n * Based on RFC 9421 (HTTP Message Signatures) and ChatGPT's implementation\n * Reference: https://help.openai.com/en/articles/9785974-chatgpt-user-allowlisting\n */\n\nimport * as ed25519 from '@noble/ed25519';\n// @ts-expect-error - @noble/hashes types are not properly resolved by pnpm\nimport { sha512 } from '@noble/hashes/sha2';\n\n// Configure @noble/ed25519 to use sync SHA-512 from @noble/hashes\n// This works in all environments including Edge Runtime\ned25519.etc.sha512Sync = (...m) => sha512(ed25519.etc.concatBytes(...m));\n\n/**\n * Known public keys for AI agents (fallback)\n *\n * IMPORTANT: These keys are used as fallback when the API is unavailable.\n * The primary source of keys should be the /api/internal/signature-keys endpoint\n * which fetches from https://chatgpt.com/.well-known/http-message-signatures-directory\n *\n * TODO: Implement automated key rotation by:\n * 1. Setting up a cron job to fetch from OpenAI's well-known endpoint\n * 2. Storing keys in database/KV store with proper expiration handling\n * 3. Removing hardcoded fallback keys entirely\n */\nconst KNOWN_KEYS = {\n chatgpt: [\n {\n kid: 'otMqcjr17mGyruktGvJU8oojQTSMHlVm7uO-lrcqbdg',\n // ChatGPT's current Ed25519 public key (base64)\n // Source: https://chatgpt.com/.well-known/http-message-signatures-directory\n publicKey: '7F_3jDlxaquwh291MiACkcS3Opq88NksyHiakzS-Y1g',\n validFrom: 1735689600, // Jan 1, 2025 (from OpenAI's nbf)\n // Extended expiration as fallback safety - API fetch should provide fresh keys\n // Check OpenAI's well-known endpoint for actual expiration dates\n validUntil: 1799625600, // Jan 1, 2027 (extended for fallback safety)\n },\n ],\n};\n\n/**\n * In-memory cache for API-fetched keys\n */\ninterface CachedKeys {\n keys: Array<{\n kid: string;\n publicKey: string;\n validFrom: number;\n validUntil: number;\n }>;\n cachedAt: number;\n}\n\nconst keyCache = new Map<string, CachedKeys>();\nconst CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes\nconst CACHE_MAX_SIZE = 100; // Maximum cache entries before cleanup\n\n/**\n * Get API base URL for fetching keys\n * Returns absolute URL for server-side, relative for browser\n */\nfunction getApiBaseUrl(): string {\n if (typeof window !== 'undefined') {\n // Browser: use relative path\n return '/api/internal';\n }\n\n // Server-side: must use absolute URL\n // Try environment variables first\n const baseUrl =\n process.env.NEXT_PUBLIC_APP_URL ||\n process.env.NEXT_PUBLIC_API_URL ||\n process.env.API_URL ||\n (process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : null);\n\n if (baseUrl) {\n return baseUrl.replace(/\\/$/, '') + '/api/internal';\n }\n\n // Fallback: try to construct from request context if available\n // For middleware/edge runtime, we may need to pass the request URL\n // For now, return relative path and log warning\n if (process.env.NODE_ENV !== 'production') {\n console.warn(\n '[Signature] No base URL configured for server-side fetch. Using localhost fallback.'\n );\n return 'http://localhost:3000/api/internal';\n }\n\n // Production fallback - should not reach here if properly configured\n console.error(\n '[Signature] CRITICAL: No base URL configured for server-side fetch in production!'\n );\n return '/api/internal'; // Will fail, but prevents silent success\n}\n\n/**\n * Clean up expired cache entries and enforce size limit\n * Called periodically to prevent unbounded memory growth\n * Uses LRU-style eviction: removes expired entries first, then oldest entries if still over limit\n */\nfunction cleanupExpiredCache(): void {\n const now = Date.now();\n const entriesToDelete: string[] = [];\n\n // First pass: remove expired entries\n for (const [agent, cached] of keyCache.entries()) {\n if (now - cached.cachedAt > CACHE_TTL_MS) {\n entriesToDelete.push(agent);\n }\n }\n\n for (const agent of entriesToDelete) {\n keyCache.delete(agent);\n }\n\n // Second pass: if still over limit, remove oldest entries (LRU eviction)\n if (keyCache.size > CACHE_MAX_SIZE) {\n // Convert entries to array with cachedAt timestamp for sorting\n const entries = Array.from(keyCache.entries()).map(([agent, cached]) => ({\n agent,\n cachedAt: cached.cachedAt,\n }));\n\n // Sort by cachedAt (oldest first)\n entries.sort((a, b) => a.cachedAt - b.cachedAt);\n\n // Remove oldest entries until we're under the limit\n const toRemove = entries.slice(0, keyCache.size - CACHE_MAX_SIZE);\n for (const entry of toRemove) {\n keyCache.delete(entry.agent);\n }\n }\n}\n\n/**\n * Fetch keys from API with caching\n */\nasync function fetchKeysFromApi(agent: string): Promise<Array<{\n kid: string;\n publicKey: string;\n validFrom: number;\n validUntil: number;\n}> | null> {\n // Periodic cleanup to prevent memory leaks\n if (keyCache.size > CACHE_MAX_SIZE) {\n cleanupExpiredCache();\n }\n\n // Check cache first\n const cached = keyCache.get(agent);\n if (cached && Date.now() - cached.cachedAt < CACHE_TTL_MS) {\n return cached.keys;\n }\n\n // Check if fetch is available (Edge Runtime compatibility)\n if (typeof fetch === 'undefined') {\n console.warn('[Signature] fetch() not available in this environment');\n return null;\n }\n\n try {\n const apiBaseUrl = getApiBaseUrl();\n const url = `${apiBaseUrl}/signature-keys?agent=${encodeURIComponent(agent)}`;\n\n const response = await fetch(url, {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n },\n // 5 second timeout\n signal: AbortSignal.timeout(5000),\n });\n\n if (!response.ok) {\n console.warn(`[Signature] Failed to fetch keys from API: ${response.status}`);\n return null;\n }\n\n const data = await response.json();\n\n if (!data.keys || !Array.isArray(data.keys) || data.keys.length === 0) {\n console.warn(`[Signature] No keys returned from API for agent: ${agent}`);\n return null;\n }\n\n // Cache the result\n keyCache.set(agent, {\n keys: data.keys,\n cachedAt: Date.now(),\n });\n\n return data.keys;\n } catch (error) {\n console.warn('[Signature] Error fetching keys from API, using fallback', {\n error: error instanceof Error ? error.message : 'Unknown error',\n agent,\n });\n return null;\n }\n}\n\n/**\n * Type guard to check if agent is a valid key in KNOWN_KEYS\n */\nfunction isValidAgent(agent: string): agent is keyof typeof KNOWN_KEYS {\n return agent in KNOWN_KEYS;\n}\n\n/**\n * Get keys for an agent (API first, then fallback)\n */\nasync function getKeysForAgent(agent: string): Promise<\n Array<{\n kid: string;\n publicKey: string;\n validFrom: number;\n validUntil: number;\n }>\n> {\n // Try API first\n const apiKeys = await fetchKeysFromApi(agent);\n if (apiKeys && apiKeys.length > 0) {\n return apiKeys;\n }\n\n // Fallback to hardcoded keys with type guard\n if (isValidAgent(agent)) {\n return KNOWN_KEYS[agent];\n }\n\n return [];\n}\n\n/**\n * Parse the Signature-Input header according to RFC 9421\n */\nfunction parseSignatureInput(signatureInput: string): {\n keyid: string;\n created?: number | undefined;\n expires?: number | undefined;\n signedHeaders: string[];\n} | null {\n try {\n // Example: sig1=(\"@method\" \"@path\" \"@authority\" \"date\");keyid=\"...\";created=1234567890\n const match = signatureInput.match(/sig1=\\((.*?)\\);(.+)/);\n if (!match) return null;\n\n const [, headersList, params] = match;\n\n // Parse signed headers\n const signedHeaders = headersList\n ? headersList\n .split(' ')\n .map((h) => h.replace(/\"/g, '').trim())\n .filter((h) => h.length > 0)\n : [];\n\n // Parse parameters\n const keyidMatch = params ? params.match(/keyid=\"([^\"]+)\"/) : null;\n const createdMatch = params ? params.match(/created=(\\d+)/) : null;\n const expiresMatch = params ? params.match(/expires=(\\d+)/) : null;\n\n if (!keyidMatch || !keyidMatch[1]) return null;\n\n return {\n keyid: keyidMatch[1],\n created: createdMatch && createdMatch[1] ? parseInt(createdMatch[1]) : undefined,\n expires: expiresMatch && expiresMatch[1] ? parseInt(expiresMatch[1]) : undefined,\n signedHeaders,\n };\n } catch (error) {\n console.error('[Signature] Failed to parse Signature-Input:', error);\n return null;\n }\n}\n\n/**\n * Build the signature base string according to RFC 9421\n * This is what gets signed\n */\nfunction buildSignatureBase(\n method: string,\n path: string,\n headers: Record<string, string>,\n signedHeaders: string[]\n): string {\n const components: string[] = [];\n\n for (const headerName of signedHeaders) {\n let value: string;\n\n switch (headerName) {\n case '@method':\n value = method.toUpperCase();\n break;\n case '@path':\n value = path;\n break;\n case '@authority':\n // Get from Host header or URL\n value = headers['host'] || headers['Host'] || '';\n break;\n default: {\n // Regular headers (case-insensitive lookup)\n const key = Object.keys(headers).find((k) => k.toLowerCase() === headerName.toLowerCase());\n value = key ? headers[key] || '' : '';\n break;\n }\n }\n\n // Format according to RFC 9421\n components.push(`\"${headerName}\": ${value}`);\n }\n\n return components.join('\\n');\n}\n\n/**\n * Decode base64 (handles both standard and URL-safe variants)\n * URL-safe base64 uses - instead of + and _ instead of /\n */\nfunction base64ToBytes(base64: string): Uint8Array {\n // Convert URL-safe base64 to standard base64\n let standardBase64 = base64.replace(/-/g, '+').replace(/_/g, '/');\n\n // Add padding if needed\n const padding = standardBase64.length % 4;\n if (padding) {\n standardBase64 += '='.repeat(4 - padding);\n }\n\n const binaryString = atob(standardBase64);\n return Uint8Array.from(binaryString, (c) => c.charCodeAt(0));\n}\n\n/**\n * Verify Ed25519 signature using @noble/ed25519 (works in all environments including Edge Runtime)\n * Falls back to Web Crypto API if available\n */\nasync function verifyEd25519Signature(\n publicKeyBase64: string,\n signatureBase64: string,\n message: string\n): Promise<boolean> {\n try {\n // Decode base64 to Uint8Array (handles URL-safe base64)\n const publicKeyBytes = base64ToBytes(publicKeyBase64);\n const signatureBytes = base64ToBytes(signatureBase64);\n const messageBytes = new TextEncoder().encode(message);\n\n // Check key and signature lengths\n if (publicKeyBytes.length !== 32) {\n console.error('[Signature] Invalid public key length:', publicKeyBytes.length);\n return false;\n }\n if (signatureBytes.length !== 64) {\n console.error('[Signature] Invalid signature length:', signatureBytes.length);\n return false;\n }\n\n // Use @noble/ed25519 with sync SHA-512 - works in all environments including Edge Runtime\n return ed25519.verify(signatureBytes, messageBytes, publicKeyBytes);\n } catch (nobleError) {\n console.warn('[Signature] @noble/ed25519 failed, trying Web Crypto fallback:', nobleError);\n\n // Fallback to Web Crypto API (may not work in Edge Runtime)\n try {\n const publicKeyBytes = base64ToBytes(publicKeyBase64);\n const signatureBytes = base64ToBytes(signatureBase64);\n const messageBytes = new TextEncoder().encode(message);\n\n const publicKey = await crypto.subtle.importKey(\n 'raw',\n publicKeyBytes.buffer as ArrayBuffer,\n {\n name: 'Ed25519',\n namedCurve: 'Ed25519',\n },\n false,\n ['verify']\n );\n\n return await crypto.subtle.verify(\n 'Ed25519',\n publicKey,\n signatureBytes.buffer as ArrayBuffer,\n messageBytes\n );\n } catch (cryptoError) {\n console.error('[Signature] Both @noble/ed25519 and Web Crypto failed:', {\n nobleError: nobleError instanceof Error ? nobleError.message : 'Unknown',\n cryptoError: cryptoError instanceof Error ? cryptoError.message : 'Unknown',\n });\n return false;\n }\n }\n}\n\n/**\n * Signature verification result\n */\nexport interface SignatureVerificationResult {\n isValid: boolean;\n agent?: string;\n keyid?: string;\n confidence: number;\n reason?: string;\n verificationMethod: 'signature' | 'none';\n}\n\n/**\n * Verify HTTP Message Signature for AI agents\n */\nexport async function verifyAgentSignature(\n method: string,\n path: string,\n headers: Record<string, string>\n): Promise<SignatureVerificationResult> {\n // Check for signature headers\n const signature = headers['signature'] || headers['Signature'];\n const signatureInput = headers['signature-input'] || headers['Signature-Input'];\n const signatureAgent = headers['signature-agent'] || headers['Signature-Agent'];\n\n // No signature present\n if (!signature || !signatureInput) {\n return {\n isValid: false,\n confidence: 0,\n reason: 'No signature headers present',\n verificationMethod: 'none',\n };\n }\n\n // Parse Signature-Input header\n const parsed = parseSignatureInput(signatureInput);\n if (!parsed) {\n return {\n isValid: false,\n confidence: 0,\n reason: 'Invalid Signature-Input header',\n verificationMethod: 'none',\n };\n }\n\n // Check timestamp if present\n if (parsed.created) {\n const now = Math.floor(Date.now() / 1000);\n const age = now - parsed.created;\n\n // Reject signatures older than 5 minutes\n if (age > 300) {\n return {\n isValid: false,\n confidence: 0,\n reason: 'Signature expired (older than 5 minutes)',\n verificationMethod: 'none',\n };\n }\n\n // Reject signatures from the future (clock skew tolerance: 30 seconds)\n if (age < -30) {\n return {\n isValid: false,\n confidence: 0,\n reason: 'Signature timestamp is in the future',\n verificationMethod: 'none',\n };\n }\n }\n\n // Determine which agent based on signature-agent header\n let agent: string | undefined;\n let agentKey: string | undefined;\n\n if (signatureAgent === '\"https://chatgpt.com\"' || signatureAgent?.includes('chatgpt.com')) {\n agent = 'ChatGPT';\n agentKey = 'chatgpt';\n }\n // Add other agents here as needed\n\n if (!agent || !agentKey) {\n return {\n isValid: false,\n confidence: 0,\n reason: 'Unknown signature agent',\n verificationMethod: 'none',\n };\n }\n\n // Get keys (API first, then fallback)\n const knownKeys = await getKeysForAgent(agentKey);\n\n if (knownKeys.length === 0) {\n return {\n isValid: false,\n confidence: 0,\n reason: 'No keys available for agent',\n verificationMethod: 'none',\n };\n }\n\n // Find the key by ID\n const key = knownKeys.find((k) => k.kid === parsed.keyid);\n if (!key) {\n return {\n isValid: false,\n confidence: 0,\n reason: `Unknown key ID: ${parsed.keyid}`,\n verificationMethod: 'none',\n };\n }\n\n // Check key validity period\n const now = Math.floor(Date.now() / 1000);\n if (now < key.validFrom || now > key.validUntil) {\n return {\n isValid: false,\n confidence: 0,\n reason: 'Key is not valid at current time',\n verificationMethod: 'none',\n };\n }\n\n // Build the signature base string\n const signatureBase = buildSignatureBase(method, path, headers, parsed.signedHeaders);\n\n // Extract the actual signature value (remove \"sig1=:\" prefix and \"::\" suffix if present)\n let signatureValue = signature;\n if (signatureValue.startsWith('sig1=:')) {\n signatureValue = signatureValue.substring(6);\n }\n if (signatureValue.endsWith(':')) {\n signatureValue = signatureValue.slice(0, -1);\n }\n\n // Verify the signature\n const isValid = await verifyEd25519Signature(key.publicKey, signatureValue, signatureBase);\n\n if (isValid) {\n return {\n isValid: true,\n agent,\n keyid: parsed.keyid,\n confidence: 1.0, // 100% confidence for valid signature\n verificationMethod: 'signature',\n };\n } else {\n return {\n isValid: false,\n confidence: 0,\n reason: 'Signature verification failed',\n verificationMethod: 'none',\n };\n }\n}\n\n/**\n * Quick check if signature headers are present (for performance)\n */\nexport function hasSignatureHeaders(headers: Record<string, string>): boolean {\n return !!(\n (headers['signature'] || headers['Signature']) &&\n (headers['signature-input'] || headers['Signature-Input'])\n );\n}\n\n/**\n * Check if this is a ChatGPT signature based on headers\n * Uses secure URL parsing to prevent spoofing attacks\n */\nexport function isChatGPTSignature(headers: Record<string, string>): boolean {\n const signatureAgent = headers['signature-agent'] || headers['Signature-Agent'];\n\n if (!signatureAgent) {\n return false;\n }\n\n // Strip leading/trailing quotes if present\n const agentUrlStr = signatureAgent.replace(/^\"+|\"+$/g, '');\n\n // Exact match for the standard ChatGPT signature agent\n if (agentUrlStr === 'https://chatgpt.com') {\n return true;\n }\n\n // Parse URL and validate host to prevent spoofing\n try {\n const agentUrl = new URL(agentUrlStr);\n const allowedHosts = ['chatgpt.com', 'www.chatgpt.com'];\n return allowedHosts.includes(agentUrl.host);\n } catch {\n // Not a valid URL, return false for security\n return false;\n }\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  import { NextRequest, NextResponse } from 'next/server';
2
- import { AgentShieldConfig, DetectionResult, AgentShieldEvents } from '@kya-os/agentshield';
2
+ import { AgentShieldConfig, DetectionResult } from '@kya-os/agentshield-shared';
3
+ import { AgentShieldEvents } from '@kya-os/agentshield';
3
4
 
4
5
  /**
5
6
  * Next.js-specific type definitions
@@ -75,6 +76,22 @@ interface NextJSMiddlewareConfig extends Partial<AgentShieldConfig> {
75
76
  encryptionKey?: string;
76
77
  };
77
78
  }
79
+ /**
80
+ * Extended NextRequest with AgentShield data
81
+ */
82
+ interface AgentShieldRequest extends NextRequest {
83
+ agentShield?: {
84
+ result?: DetectionResult;
85
+ skipped: boolean;
86
+ session?: {
87
+ id: string;
88
+ agent: string;
89
+ confidence: number;
90
+ detectedAt: number;
91
+ expires: number;
92
+ };
93
+ };
94
+ }
78
95
  /**
79
96
  * Detection context for hooks
80
97
  */
@@ -85,4 +102,4 @@ interface DetectionContext {
85
102
  ip?: string;
86
103
  }
87
104
 
88
- export type { DetectionContext as D, NextJSMiddlewareConfig as N };
105
+ export type { AgentShieldRequest as A, DetectionContext as D, NextJSMiddlewareConfig as N };
@@ -1,5 +1,6 @@
1
1
  import { NextRequest, NextResponse } from 'next/server';
2
- import { AgentShieldConfig, DetectionResult, AgentShieldEvents } from '@kya-os/agentshield';
2
+ import { AgentShieldConfig, DetectionResult } from '@kya-os/agentshield-shared';
3
+ import { AgentShieldEvents } from '@kya-os/agentshield';
3
4
 
4
5
  /**
5
6
  * Next.js-specific type definitions
@@ -75,6 +76,22 @@ interface NextJSMiddlewareConfig extends Partial<AgentShieldConfig> {
75
76
  encryptionKey?: string;
76
77
  };
77
78
  }
79
+ /**
80
+ * Extended NextRequest with AgentShield data
81
+ */
82
+ interface AgentShieldRequest extends NextRequest {
83
+ agentShield?: {
84
+ result?: DetectionResult;
85
+ skipped: boolean;
86
+ session?: {
87
+ id: string;
88
+ agent: string;
89
+ confidence: number;
90
+ detectedAt: number;
91
+ expires: number;
92
+ };
93
+ };
94
+ }
78
95
  /**
79
96
  * Detection context for hooks
80
97
  */
@@ -85,4 +102,4 @@ interface DetectionContext {
85
102
  ip?: string;
86
103
  }
87
104
 
88
- export type { DetectionContext as D, NextJSMiddlewareConfig as N };
105
+ export type { AgentShieldRequest as A, DetectionContext as D, NextJSMiddlewareConfig as N };
@@ -8,7 +8,8 @@ function createWasmAgentShieldMiddleware(config) {
8
8
  const {
9
9
  onAgentDetected,
10
10
  blockOnHighConfidence = false,
11
- confidenceThreshold = 0.8,
11
+ confidenceThreshold = 80,
12
+ // Updated to 0-100 scale (was 0.8)
12
13
  skipPaths = [],
13
14
  blockedResponse = {
14
15
  status: 403,
@@ -34,14 +35,22 @@ function createWasmAgentShieldMiddleware(config) {
34
35
  const result = await detector.analyze(metadata);
35
36
  const enhancedResult = {
36
37
  isAgent: result.isAgent,
37
- confidence: hasWasm && result.confidence > 0.85 ? Math.min(result.confidence * 1.15, 1) : (
38
- // Boost confidence with WASM
38
+ confidence: hasWasm && result.confidence > 85 ? (
39
+ // Updated to 0-100 scale (was 0.85)
40
+ Math.min(result.confidence * 1.15, 100)
41
+ ) : (
42
+ // Boost confidence with WASM, cap at 100
39
43
  result.confidence
40
44
  ),
41
45
  agent: result.detectedAgent?.name || void 0,
42
- verificationMethod: hasWasm && result.confidence > 0.85 ? "signature" : "pattern",
43
- riskLevel: result.confidence > 0.9 ? "high" : result.confidence > 0.7 ? "medium" : "low",
44
- timestamp: result.timestamp.toISOString()
46
+ verificationMethod: hasWasm && result.confidence > 85 ? "signature" : "pattern",
47
+ // Updated to 0-100 scale
48
+ riskLevel: result.confidence > 90 ? "high" : (
49
+ // Updated to 0-100 scale (was 0.9)
50
+ result.confidence > 70 ? "medium" : "low"
51
+ ),
52
+ // Updated to 0-100 scale (was 0.7)
53
+ timestamp: result.timestamp instanceof Date ? result.timestamp.toISOString() : new Date(result.timestamp).toISOString()
45
54
  };
46
55
  if (onAgentDetected && enhancedResult.isAgent) {
47
56
  await onAgentDetected(enhancedResult);
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/wasm-middleware.ts"],"names":["NextResponse","AgentDetector"],"mappings":"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","file":"wasm-middleware.js","sourcesContent":["/**\n * WASM-enabled middleware for Next.js with AgentShield\n * Following official Next.js documentation for WebAssembly in Edge Runtime\n */\n\nimport type { NextRequest } from 'next/server';\nimport { NextResponse } from 'next/server';\nimport { AgentDetector } from '@kya-os/agentshield';\n\n// Type definitions for WASM detection result\nexport interface WasmDetectionResult {\n isAgent: boolean;\n confidence: number;\n agent?: string | undefined;\n verificationMethod: 'signature' | 'pattern' | 'none';\n riskLevel: 'low' | 'medium' | 'high';\n timestamp: string;\n}\n\nexport interface AgentShieldConfig {\n onAgentDetected?: (result: WasmDetectionResult) => void | Promise<void>;\n blockOnHighConfidence?: boolean;\n confidenceThreshold?: number;\n skipPaths?: string[];\n blockedResponse?: {\n status?: number;\n message?: string;\n headers?: Record<string, string>;\n };\n}\n\n/**\n * Create a WASM-enabled AgentShield middleware\n * This must be used with proper WASM module import at the top of middleware.ts\n * \n * @example\n * ```typescript\n * // middleware.ts\n * import wasmModule from '@kya-os/agentshield/wasm?module';\n * import { createWasmAgentShieldMiddleware } from '@kya-os/agentshield-nextjs';\n * \n * const wasmInstance = await WebAssembly.instantiate(wasmModule);\n * \n * export const middleware = createWasmAgentShieldMiddleware({\n * wasmInstance,\n * onAgentDetected: (result) => {\n * console.log(`Detected ${result.agent} with ${result.confidence * 100}% confidence`);\n * }\n * });\n * ```\n */\nexport function createWasmAgentShieldMiddleware(config: AgentShieldConfig & {\n wasmInstance?: WebAssembly.Instance;\n}) {\n const {\n onAgentDetected,\n blockOnHighConfidence = false,\n confidenceThreshold = 0.8,\n skipPaths = [],\n blockedResponse = {\n status: 403,\n message: 'Access denied: AI agent detected',\n headers: { 'Content-Type': 'application/json' }\n },\n wasmInstance\n } = config;\n\n return async function middleware(request: NextRequest) {\n // Check if path should be skipped\n const path = request.nextUrl.pathname;\n if (skipPaths.some(skip => path.startsWith(skip))) {\n return NextResponse.next();\n }\n\n try {\n // Create detector with or without WASM\n const detector = new AgentDetector();\n \n // If WASM instance is provided, we'll have higher confidence\n const hasWasm = !!wasmInstance;\n \n // Prepare request metadata\n const metadata = {\n userAgent: request.headers.get('user-agent') || undefined,\n ipAddress: request.headers.get('x-forwarded-for') || \n request.headers.get('x-real-ip') || \n undefined,\n headers: Object.fromEntries(request.headers.entries()),\n timestamp: new Date()\n };\n\n // Perform detection\n const result = await detector.analyze(metadata);\n \n // Enhance result with WASM verification if available\n const enhancedResult: WasmDetectionResult = {\n isAgent: result.isAgent,\n confidence: hasWasm && result.confidence > 0.85 ? \n Math.min(result.confidence * 1.15, 1.0) : // Boost confidence with WASM\n result.confidence,\n agent: result.detectedAgent?.name || undefined,\n verificationMethod: hasWasm && result.confidence > 0.85 ? 'signature' : 'pattern',\n riskLevel: result.confidence > 0.9 ? 'high' : \n result.confidence > 0.7 ? 'medium' : 'low',\n timestamp: result.timestamp.toISOString()\n };\n\n // Call user callback if provided\n if (onAgentDetected && enhancedResult.isAgent) {\n await onAgentDetected(enhancedResult);\n }\n\n // Block if configured and confidence is high\n if (blockOnHighConfidence && \n enhancedResult.isAgent && \n enhancedResult.confidence >= confidenceThreshold) {\n \n return NextResponse.json(\n { \n error: blockedResponse.message,\n agent: enhancedResult.agent,\n confidence: Math.round(enhancedResult.confidence * 100)\n },\n { \n status: blockedResponse.status || 403,\n headers: blockedResponse.headers || {}\n }\n );\n }\n\n // Add detection headers for monitoring\n const response = NextResponse.next();\n if (enhancedResult.isAgent) {\n response.headers.set('X-Agent-Detected', enhancedResult.agent || 'unknown');\n response.headers.set('X-Agent-Confidence', String(Math.round(enhancedResult.confidence * 100)));\n response.headers.set('X-Agent-Verification', enhancedResult.verificationMethod);\n }\n\n return response;\n\n } catch (error) {\n console.error('AgentShield middleware error:', error);\n // On error, continue without blocking\n return NextResponse.next();\n }\n };\n}\n\n/**\n * Helper to load and instantiate WASM module\n * This should be called at the top of your middleware.ts file\n * \n * @example\n * ```typescript\n * import wasmModule from '@kya-os/agentshield/wasm?module';\n * const wasmInstance = await instantiateWasm(wasmModule);\n * ```\n */\nexport async function instantiateWasm(wasmModule: WebAssembly.Module): Promise<WebAssembly.Instance> {\n try {\n const instance = await WebAssembly.instantiate(wasmModule);\n console.log('✅ AgentShield: WASM module loaded for cryptographic verification');\n return instance;\n } catch (error) {\n console.warn('⚠️ AgentShield: Failed to instantiate WASM module', error);\n throw error;\n }\n}"]}
1
+ {"version":3,"sources":["../src/wasm-middleware.ts"],"names":["NextResponse","AgentDetector"],"mappings":"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","file":"wasm-middleware.js","sourcesContent":["/**\n * WASM-enabled middleware for Next.js with AgentShield\n * Following official Next.js documentation for WebAssembly in Edge Runtime\n */\n\nimport type { NextRequest } from 'next/server';\nimport { NextResponse } from 'next/server';\nimport { AgentDetector } from '@kya-os/agentshield';\n\n// Type definitions for WASM detection result\nexport interface WasmDetectionResult {\n isAgent: boolean;\n confidence: number;\n agent?: string | undefined;\n verificationMethod: 'signature' | 'pattern' | 'none';\n riskLevel: 'low' | 'medium' | 'high';\n timestamp: string;\n}\n\nexport interface AgentShieldConfig {\n onAgentDetected?: (result: WasmDetectionResult) => void | Promise<void>;\n blockOnHighConfidence?: boolean;\n confidenceThreshold?: number;\n skipPaths?: string[];\n blockedResponse?: {\n status?: number;\n message?: string;\n headers?: Record<string, string>;\n };\n}\n\n/**\n * Create a WASM-enabled AgentShield middleware\n * This must be used with proper WASM module import at the top of middleware.ts\n * \n * @example\n * ```typescript\n * // middleware.ts\n * import wasmModule from '@kya-os/agentshield/wasm?module';\n * import { createWasmAgentShieldMiddleware } from '@kya-os/agentshield-nextjs';\n * \n * const wasmInstance = await WebAssembly.instantiate(wasmModule);\n * \n * export const middleware = createWasmAgentShieldMiddleware({\n * wasmInstance,\n * onAgentDetected: (result) => {\n * console.log(`Detected ${result.agent} with ${result.confidence * 100}% confidence`);\n * }\n * });\n * ```\n */\nexport function createWasmAgentShieldMiddleware(config: AgentShieldConfig & {\n wasmInstance?: WebAssembly.Instance;\n}) {\n const {\n onAgentDetected,\n blockOnHighConfidence = false,\n confidenceThreshold = 80, // Updated to 0-100 scale (was 0.8)\n skipPaths = [],\n blockedResponse = {\n status: 403,\n message: 'Access denied: AI agent detected',\n headers: { 'Content-Type': 'application/json' }\n },\n wasmInstance\n } = config;\n\n return async function middleware(request: NextRequest) {\n // Check if path should be skipped\n const path = request.nextUrl.pathname;\n if (skipPaths.some(skip => path.startsWith(skip))) {\n return NextResponse.next();\n }\n\n try {\n // Create detector with or without WASM\n const detector = new AgentDetector();\n \n // If WASM instance is provided, we'll have higher confidence\n const hasWasm = !!wasmInstance;\n \n // Prepare request metadata\n const metadata = {\n userAgent: request.headers.get('user-agent') || undefined,\n ipAddress: request.headers.get('x-forwarded-for') || \n request.headers.get('x-real-ip') || \n undefined,\n headers: Object.fromEntries(request.headers.entries()),\n timestamp: new Date()\n };\n\n // Perform detection\n const result = await detector.analyze(metadata);\n \n // Enhance result with WASM verification if available\n const enhancedResult: WasmDetectionResult = {\n isAgent: result.isAgent,\n confidence: hasWasm && result.confidence > 85 ? // Updated to 0-100 scale (was 0.85)\n Math.min(result.confidence * 1.15, 100) : // Boost confidence with WASM, cap at 100\n result.confidence,\n agent: result.detectedAgent?.name || undefined,\n verificationMethod: hasWasm && result.confidence > 85 ? 'signature' : 'pattern', // Updated to 0-100 scale\n riskLevel: result.confidence > 90 ? 'high' : // Updated to 0-100 scale (was 0.9)\n result.confidence > 70 ? 'medium' : 'low', // Updated to 0-100 scale (was 0.7)\n timestamp: result.timestamp instanceof Date \n ? result.timestamp.toISOString() \n : new Date(result.timestamp).toISOString()\n };\n\n // Call user callback if provided\n if (onAgentDetected && enhancedResult.isAgent) {\n await onAgentDetected(enhancedResult);\n }\n\n // Block if configured and confidence is high\n if (blockOnHighConfidence && \n enhancedResult.isAgent && \n enhancedResult.confidence >= confidenceThreshold) {\n \n return NextResponse.json(\n { \n error: blockedResponse.message,\n agent: enhancedResult.agent,\n confidence: Math.round(enhancedResult.confidence * 100)\n },\n { \n status: blockedResponse.status || 403,\n headers: blockedResponse.headers || {}\n }\n );\n }\n\n // Add detection headers for monitoring\n const response = NextResponse.next();\n if (enhancedResult.isAgent) {\n response.headers.set('X-Agent-Detected', enhancedResult.agent || 'unknown');\n response.headers.set('X-Agent-Confidence', String(Math.round(enhancedResult.confidence * 100)));\n response.headers.set('X-Agent-Verification', enhancedResult.verificationMethod);\n }\n\n return response;\n\n } catch (error) {\n console.error('AgentShield middleware error:', error);\n // On error, continue without blocking\n return NextResponse.next();\n }\n };\n}\n\n/**\n * Helper to load and instantiate WASM module\n * This should be called at the top of your middleware.ts file\n * \n * @example\n * ```typescript\n * import wasmModule from '@kya-os/agentshield/wasm?module';\n * const wasmInstance = await instantiateWasm(wasmModule);\n * ```\n */\nexport async function instantiateWasm(wasmModule: WebAssembly.Module): Promise<WebAssembly.Instance> {\n try {\n const instance = await WebAssembly.instantiate(wasmModule);\n console.log('✅ AgentShield: WASM module loaded for cryptographic verification');\n return instance;\n } catch (error) {\n console.warn('⚠️ AgentShield: Failed to instantiate WASM module', error);\n throw error;\n }\n}"]}
@@ -6,7 +6,8 @@ function createWasmAgentShieldMiddleware(config) {
6
6
  const {
7
7
  onAgentDetected,
8
8
  blockOnHighConfidence = false,
9
- confidenceThreshold = 0.8,
9
+ confidenceThreshold = 80,
10
+ // Updated to 0-100 scale (was 0.8)
10
11
  skipPaths = [],
11
12
  blockedResponse = {
12
13
  status: 403,
@@ -32,14 +33,22 @@ function createWasmAgentShieldMiddleware(config) {
32
33
  const result = await detector.analyze(metadata);
33
34
  const enhancedResult = {
34
35
  isAgent: result.isAgent,
35
- confidence: hasWasm && result.confidence > 0.85 ? Math.min(result.confidence * 1.15, 1) : (
36
- // Boost confidence with WASM
36
+ confidence: hasWasm && result.confidence > 85 ? (
37
+ // Updated to 0-100 scale (was 0.85)
38
+ Math.min(result.confidence * 1.15, 100)
39
+ ) : (
40
+ // Boost confidence with WASM, cap at 100
37
41
  result.confidence
38
42
  ),
39
43
  agent: result.detectedAgent?.name || void 0,
40
- verificationMethod: hasWasm && result.confidence > 0.85 ? "signature" : "pattern",
41
- riskLevel: result.confidence > 0.9 ? "high" : result.confidence > 0.7 ? "medium" : "low",
42
- timestamp: result.timestamp.toISOString()
44
+ verificationMethod: hasWasm && result.confidence > 85 ? "signature" : "pattern",
45
+ // Updated to 0-100 scale
46
+ riskLevel: result.confidence > 90 ? "high" : (
47
+ // Updated to 0-100 scale (was 0.9)
48
+ result.confidence > 70 ? "medium" : "low"
49
+ ),
50
+ // Updated to 0-100 scale (was 0.7)
51
+ timestamp: result.timestamp instanceof Date ? result.timestamp.toISOString() : new Date(result.timestamp).toISOString()
43
52
  };
44
53
  if (onAgentDetected && enhancedResult.isAgent) {
45
54
  await onAgentDetected(enhancedResult);