@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.
- package/dist/.tsbuildinfo +1 -0
- package/dist/api-client.d.mts +145 -0
- package/dist/api-client.d.ts +145 -0
- package/dist/api-client.js +130 -0
- package/dist/api-client.js.map +1 -0
- package/dist/api-client.mjs +126 -0
- package/dist/api-client.mjs.map +1 -0
- package/dist/api-middleware.d.mts +118 -0
- package/dist/api-middleware.d.ts +118 -0
- package/dist/api-middleware.js +295 -0
- package/dist/api-middleware.js.map +1 -0
- package/dist/api-middleware.mjs +292 -0
- package/dist/api-middleware.mjs.map +1 -0
- package/dist/create-middleware.d.mts +2 -1
- package/dist/create-middleware.d.ts +2 -1
- package/dist/create-middleware.js +474 -200
- package/dist/create-middleware.js.map +1 -1
- package/dist/create-middleware.mjs +454 -200
- package/dist/create-middleware.mjs.map +1 -1
- package/dist/edge/index.d.mts +110 -0
- package/dist/edge/index.d.ts +110 -0
- package/dist/edge/index.js +253 -0
- package/dist/edge/index.js.map +1 -0
- package/dist/edge/index.mjs +251 -0
- package/dist/edge/index.mjs.map +1 -0
- package/dist/edge-detector-wrapper.d.mts +6 -15
- package/dist/edge-detector-wrapper.d.ts +6 -15
- package/dist/edge-detector-wrapper.js +314 -95
- package/dist/edge-detector-wrapper.js.map +1 -1
- package/dist/edge-detector-wrapper.mjs +294 -95
- package/dist/edge-detector-wrapper.mjs.map +1 -1
- package/dist/edge-runtime-loader.d.mts +1 -1
- package/dist/edge-runtime-loader.d.ts +1 -1
- package/dist/edge-runtime-loader.js +10 -25
- package/dist/edge-runtime-loader.js.map +1 -1
- package/dist/edge-runtime-loader.mjs +11 -23
- package/dist/edge-runtime-loader.mjs.map +1 -1
- package/dist/edge-wasm-middleware.js +2 -1
- package/dist/edge-wasm-middleware.js.map +1 -1
- package/dist/edge-wasm-middleware.mjs +2 -1
- package/dist/edge-wasm-middleware.mjs.map +1 -1
- package/dist/enhanced-middleware.d.mts +153 -0
- package/dist/enhanced-middleware.d.ts +153 -0
- package/dist/enhanced-middleware.js +1074 -0
- package/dist/enhanced-middleware.js.map +1 -0
- package/dist/enhanced-middleware.mjs +1072 -0
- package/dist/enhanced-middleware.mjs.map +1 -0
- package/dist/index.d.mts +8 -153
- package/dist/index.d.ts +8 -153
- package/dist/index.js +821 -233
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +797 -234
- package/dist/index.mjs.map +1 -1
- package/dist/middleware.d.mts +2 -1
- package/dist/middleware.d.ts +2 -1
- package/dist/middleware.js +474 -200
- package/dist/middleware.js.map +1 -1
- package/dist/middleware.mjs +454 -200
- package/dist/middleware.mjs.map +1 -1
- package/dist/session-tracker.d.mts +1 -1
- package/dist/session-tracker.d.ts +1 -1
- package/dist/session-tracker.js.map +1 -1
- package/dist/session-tracker.mjs.map +1 -1
- package/dist/signature-verifier.d.mts +1 -0
- package/dist/signature-verifier.d.ts +1 -0
- package/dist/signature-verifier.js +204 -44
- package/dist/signature-verifier.js.map +1 -1
- package/dist/signature-verifier.mjs +184 -44
- package/dist/signature-verifier.mjs.map +1 -1
- package/dist/{types-BJTEUa4T.d.mts → types-DVmy9NE3.d.mts} +19 -2
- package/dist/{types-BJTEUa4T.d.ts → types-DVmy9NE3.d.ts} +19 -2
- package/dist/wasm-middleware.js +15 -6
- package/dist/wasm-middleware.js.map +1 -1
- package/dist/wasm-middleware.mjs +15 -6
- package/dist/wasm-middleware.mjs.map +1 -1
- package/package.json +27 -6
- package/wasm/agentshield_wasm.js +209 -152
- package/wasm/agentshield_wasm_bg.wasm +0 -0
- package/wasm/package.json +30 -0
package/dist/middleware.mjs
CHANGED
|
@@ -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/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:
|
|
13
|
-
|
|
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 =
|
|
66
|
-
const signatureBytes =
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
|
268
|
+
let agentKey;
|
|
149
269
|
if (signatureAgent === '"https://chatgpt.com"' || signatureAgent?.includes("chatgpt.com")) {
|
|
150
270
|
agent = "ChatGPT";
|
|
151
|
-
|
|
271
|
+
agentKey = "chatgpt";
|
|
152
272
|
}
|
|
153
|
-
if (!agent || !
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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
|
|
305
|
-
|
|
306
|
-
|
|
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
|
-
|
|
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
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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 <
|
|
329
|
-
confidence = Math.min(confidence * 1.2,
|
|
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 >
|
|
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 && {
|
|
337
|
-
|
|
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
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
658
|
+
if (process.env.NODE_ENV !== "production") {
|
|
659
|
+
console.debug("[AgentShield] \u2705 WASM module initialized via fallback");
|
|
660
|
+
}
|
|
456
661
|
} catch (fetchError) {
|
|
457
|
-
|
|
458
|
-
|
|
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(
|
|
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
|
-
|
|
568
|
-
|
|
569
|
-
|
|
764
|
+
if (wasmAvailable) {
|
|
765
|
+
if (this.baseUrl) {
|
|
766
|
+
setWasmBaseUrl(this.baseUrl);
|
|
767
|
+
}
|
|
768
|
+
await initWasm();
|
|
769
|
+
this.wasmEnabled = true;
|
|
570
770
|
} else {
|
|
571
|
-
|
|
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 =
|
|
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,
|
|
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
|
|
607
|
-
|
|
608
|
-
const
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
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
|
|
626
|
-
|
|
627
|
-
|
|
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
|
-
|
|
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
|
-
|
|
642
|
-
|
|
643
|
-
|
|
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,
|
|
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 >
|
|
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 && {
|
|
658
|
-
|
|
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 >
|
|
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 >=
|
|
693
|
-
patternResult.confidence = Math.min(
|
|
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
|
-
|
|
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
|
-
|
|
1174
|
+
detectionClass: { type: "AiAgent" },
|
|
1175
|
+
detectedAgent: {
|
|
1176
|
+
type: "ai_agent",
|
|
1177
|
+
name: existingSession.agent
|
|
1178
|
+
},
|
|
924
1179
|
timestamp: /* @__PURE__ */ new Date(),
|
|
925
|
-
verificationMethod: "
|
|
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 ??
|
|
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
|
-
(
|
|
976
|
-
|
|
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
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
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 ??
|
|
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 ??
|
|
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);
|